diff options
351 files changed, 26122 insertions, 17126 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 22113b913..da5990956 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -29,3 +29,12 @@ If you are writing new C code, please follow the style described in Suggested ways to work on your development version (compile and run the tests without interfering with system packages) are described in ``doc/source/dev/development_environment.rst``. + +### A note on feature enhancements/API changes + +If you are interested in adding a new feature to NumPy, consider +submitting your feature proposal to the [mailing list][mail], +which is the preferred forum for discussing new features and +API changes. + +[mail]: https://mail.python.org/mailman/listinfo/numpy-discussion diff --git a/.gitignore b/.gitignore index a203abd5b..c58b0e62f 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ GRTAGS GSYMS GTAGS .cache +.mypy_cache/ # Compiled source # ################### @@ -74,6 +75,7 @@ doc/cdoc/build ./.shelf MANIFEST .cache +pip-wheel-metadata # Paver generated files # ######################### @@ -105,6 +107,11 @@ Thumbs.db ########################## /.pytest_cache +# doc build generated files # +############################# +doc/source/savefig/ + + # Things specific to this project # ################################### numpy/core/__svn_version__.py @@ -129,6 +136,7 @@ numpy/core/src/common/npy_cpu_features.c numpy/core/src/common/npy_partition.h numpy/core/src/common/npy_sort.h numpy/core/src/common/templ_common.h +numpy/core/src/common/_cpu_dispatch.h numpy/core/src/multiarray/_multiarray_tests.c numpy/core/src/multiarray/arraytypes.c numpy/core/src/multiarray/einsum.c @@ -164,6 +172,9 @@ numpy/core/src/umath/simd.inc numpy/core/src/umath/struct_ufunc_test.c numpy/core/src/umath/test_rational.c numpy/core/src/umath/umath_tests.c +numpy/core/src/umath/_umath_tests.dispatch.avx2.c +numpy/core/src/umath/_umath_tests.dispatch.h +numpy/core/src/umath/_umath_tests.dispatch.sse41.c numpy/distutils/__config__.py numpy/linalg/umath_linalg.c doc/source/**/generated/ @@ -32,6 +32,7 @@ Amir Sarabadani <ladsgroup@gmail.com> amir <ladsgroup@gmail.com> Anatoly Techtonik <techtonik@gmail.com> anatoly techtonik <techtonik@gmail.com> Andras Deak <deak.andris@gmail.com> adeak <adeak@users.noreply.github.com> Andrea Pattori <andrea.pattori@gmail.com> patto90 <andrea.pattori@gmail.com> +Andrea Sangalli <and-sang@outlook.com> and-sang <53617841+and-sang@users.noreply.github.com> Andrei Kucharavy <ank@andreikucharavy.com> chiffa <ank@andreikucharavy.com> Anne Archibald <peridot.faceted@gmail.com> aarchiba <peridot.faceted@gmail.com> Anne Archibald <peridot.faceted@gmail.com> Anne Archibald <archibald@astron.nl> @@ -55,6 +56,7 @@ Bob Eldering <eldering@jive.eu> bobeldering <eldering@jive.eu> Brett R Murphy <bmurphy@enthought.com> brettrmurphy <bmurphy@enthought.com> Bryan Van de Ven <bryanv@continuum.io> Bryan Van de Ven <bryan@Laptop-3.local> Bryan Van de Ven <bryanv@continuum.io> Bryan Van de Ven <bryan@laptop.local> +Bui Duc Minh <buiducminh287@gmail.com> Mibu287 <41239569+Mibu287@users.noreply.github.com> Carl Kleffner <cmkleffner@gmail.com> carlkl <cmkleffner@gmail.com> Chris Burns <chris.burns@localhost> chris.burns <chris.burns@localhost> Chris Kerr <debdepba@dasganma.tk> Chris Kerr <cjk34@cam.ac.uk> @@ -63,6 +65,7 @@ Christopher Hanley <chanley@gmail.com> chanley <chanley@gmail.com> Christoph Gohlke <cgohlke@uci.edu> cgholke <?@?> Christoph Gohlke <cgohlke@uci.edu> cgohlke <cgohlke@uci.edu> Christoph Gohlke <cgohlke@uci.edu> Christolph Gohlke <cgohlke@uci.edu> +Chunlin Fang <fangchunlin@huawei.com> Qiyu8 <fangchunlin@huawei.com> Colin Snyder <47012605+colinsnyder@users.noreply.github.com> colinsnyder <47012605+colinsnyder@users.noreply.github.com> Daniel B Allan <daniel.b.allan@gmail.com> danielballan <daniel.b.allan@gmail.com> Daniel da Silva <mail@danieldasilva.org> Daniel da Silva <daniel@meltingwax.net> @@ -83,9 +86,12 @@ Derek Homeier <derek@astro.physik.uni-goettingen.de> Derek Homeir <derek@astro.p Derek Homeier <derek@astro.physik.uni-goettingen.de> Derek Homier <derek@astro.physik.uni-goettingen.de> Derrick Williams <myutat@gmail.com> derrick <myutat@gmail.com> Dmitriy Shalyga <zuko3d@gmail.com> zuko3d <zuko3d@gmail.com> +Dustan Levenstein <dlevenstein@gmail.com> dustanlevenstein <43019642+dustanlevenstein@users.noreply.github.com> Ed Schofield <edschofield@localhost> edschofield <edschofield@localhost> Egor Zindy <ezindy@gmail.com> zindy <ezindy@gmail.com> Endolith <endolith@gmail.com> +Erik M. Bray <erik.bray@lri.fr> E. M. Bray <erik.bray@lri.fr> +Erik M. Bray <erik.bray@lri.fr> Erik Bray <erik.m.bray@gmail.com> Eric Fode <ericfode@gmail.com> Eric Fode <ericfode@linuxlaptop.(none)> Eric Quintero <eric.antonio.quintero@gmail.com> e-q <eric.antonio.quintero@gmail.com> Ernest N. Mamikonyan <ernest.mamikonyan@gmail.com> mamikony <ernest.mamikonyan@sig.com> @@ -111,6 +117,7 @@ Hanno Klemm <hanno.klemm@maerskoil.com> hklemm <hanno.klemm@maerskoil.com> Hemil Desai <desai38@purdue.edu> hemildesai <desai38@purdue.edu> Hiroyuki V. Yamazaki <hiroyuki.vincent.yamazaki@gmail.com> hvy <hiroyuki.vincent.yamazaki@gmail.com> Gerhard Hobler <gerhard.hobler@tuwien.ac.at> hobler <gerhard.hobler@tuwien.ac.at> +Guillaume Peillex <guillaume.peillex@gmail.com> hippo91 <guillaume.peillex@gmail.com> Irvin Probst <irvin.probst@ensta-bretagne.fr> I--P <irvin.probst@ensta-bretagne.fr> Jaime Fernandez <jaime.frio@gmail.com> Jaime Fernandez <jaime.fernandez@hp.com> Jaime Fernandez <jaime.frio@gmail.com> jaimefrio <jaime.frio@gmail.com> @@ -129,6 +136,7 @@ Johannes Hampp <johannes.hampp@zeu.uni-giessen.de> euronion <42553970+euronion@u Johannes Schönberger <hannesschoenberger@gmail.com> Johannes Schönberger <jschoenberger@demuc.de> Johann Faouzi <johann.faouzi@gmail.com> johann.faouzi <johann.faouzi@icm-institute.org> John Darbyshire <24256554+attack68@users.noreply.github.com> attack68 <24256554+attack68@users.noreply.github.com> +John Kirkham <kirkhamj@janelia.hhmi.org> jakirkham <jakirkham@gmail.com> Joseph Fox-Rabinovitz <jfoxrabinovitz@gmail.com> Joseph Fox-Rabinovitz <joseph.r.fox-rabinovitz@nasa.gov> Joseph Fox-Rabinovitz <jfoxrabinovitz@gmail.com> Joseph Fox-Rabinovitz <madphysicist@users.noreply.github.com> Joseph Fox-Rabinovitz <jfoxrabinovitz@gmail.com> Mad Physicist <madphysicist@users.noreply.github.com> @@ -138,6 +146,7 @@ Julian Taylor <juliantaylor108@gmail.com> Julian Taylor <juliantaylor108@googlem Julien Lhermitte <jrmlhermitte@gmail.com> Julien Lhermitte <lhermitte@bnl.gov> Julien Schueller <julien.schueller@gmail.com> jschueller <julien.schueller@gmail.com> Justus Magin <keewis@posteo.de> keewis <keewis@users.noreply.github.com> +Justus Magin <keewis@posteo.de> Keewis <keewis@posteo.de> Kai Striega <kaistriega@gmail.com> kai <kaistriega@gmail.com> Kai Striega <kaistriega@gmail.com> kai-striega <kaistriega@gmail.com> Kai Striega <kaistriega@gmail.com> kai-striega <kaistriega+github@gmail.com> @@ -155,6 +164,7 @@ Luke Zoltan Kelley <lkelley@cfa.harvard.edu> lzkelley <lkelley@cfa.harvard.edu> Magdalena Proszewska <magdalena.proszewska@gmail.com> mpro <magdalena.proszewska@gmail.com> Magdalena Proszewska <magdalena.proszewska@gmail.com> mproszewska <38814059+mproszewska@users.noreply.github.com> Manoj Kumar <manojkumarsivaraj334@gmail.com> MechCoder <manojkumarsivaraj334@gmail.com> +Marcin Podhajski <podhajskimarcin@gmail.com> m-podhajski <36967358+m-podhajski@users.noreply.github.com> Mark DePristo <mdepristo@synapdx.com> markdepristo <mdepristo@synapdx.com> Mark Weissman <mw9050@gmail.com> m-d-w <mw9050@gmail.com> Mark Wiebe <mwwiebe@gmail.com> Mark <mwwiebe@gmail.com> @@ -164,16 +174,21 @@ Mark Wiebe <mwwiebe@gmail.com> Mark Wiebe <mwiebe@georg.(none)> Martin Goodson <martingoodson@gmail.com> martingoodson <martingoodson@gmail.com> Martin Reinecke <martin@mpa-garching.mpg.de> mreineck <martin@mpa-garching.mpg.de> Martin Teichmann <martin.teichmann@xfel.eu> Martin Teichmann <lkb.teichmann@gmail.com> +Matt Hancock <not.matt.hancock@gmail.com> matt <mhancock743@gmail.com> Martino Sorbaro <martino.sorbaro@ed.ac.uk> martinosorb <martino.sorbaro@ed.ac.uk> Mattheus Ueckermann <empeeu@yahoo.com> empeeu <empeeu@yahoo.com> Matthew Harrigan <harrigan.matthew@gmail.com> MattHarrigan <harrigan.matthew@gmail.com> Matti Picus <matti.picus@gmail.com> mattip <matti.picus@gmail.com> +Maximilian Konrad <maximilianlukaskonrad@hotmail.de> MLK97 <maximilianlukaskonrad@hotmail.de> +Melissa Weber Mendonça <melissawm@gmail.com> Melissa Weber Mendonca <melissawm@gmail.com> +Melissa Weber Mendonça <melissawm@gmail.com> melissawm <melissawm@gmail.com> Michael Behrisch <oss@behrisch.de> behrisch <behrisch@users.sourceforge.net> Michael Droettboom <mdboom@gmail.com> mdroe <mdroe@localhost> Michael K. Tran <trankmichael@gmail.com> mtran <trankmichael@gmail.com> Michael Martin <mmartin4242@gmail.com> mmartin <mmartin4242@gmail.com> Michael Schnaitter <schnaitterm@knights.ucf.edu> schnaitterm <schnaitterm@users.noreply.github.com> Muhammad Kasim <firman.kasim@gmail.com> mfkasim91 <firman.kasim@gmail.com> +Masashi Kishimoto <drehbleistift@gmail.com> kishimoto-banana <drehbleistift@gmail.com> Nathaniel J. Smith <njs@pobox.com> njsmith <njs@pobox.com> Naveen Arunachalam <notatroll.troll@gmail.com> naveenarun <notatroll.troll@gmail.com> Nicolas Scheffer <nicolas.scheffer@sri.com> Nicolas Scheffer <scheffer@speech.sri.com> @@ -190,6 +205,7 @@ Peter J Cock <p.j.a.cock@googlemail.com> peterjc <p.j.a.cock@googlemail.com> Phil Elson <pelson.pub@gmail.com> Pierre GM <pierregmcode@gmail.com> pierregm <pierregmcode@gmail.com> Pierre GM <pierregmcode@gmail.com> pierregm <pierregm@localhost> +Piotr Gaiński <dociebieaniuszlem@gmail.com> panpiort8 <dociebieaniuszlem@gmail.com> Prabhu Ramachandran <prabhu@localhost> prabhu <prabhu@localhost> Przemyslaw Bartosik <sendthenote@gmail.com> przemb <sendthenote@gmail.com> Ralf Gommers <ralf.gommers@gmail.com> Ralf Gommers <ralf.gommers@googlemail.com> @@ -212,6 +228,7 @@ Sebastian Berg <sebastian@sipsolutions.net> seberg <sebastian@sipsolutions.net> Shekhar Prasad Rajak <shekharrajak@live.com> shekharrajak <shekharrajak@live.com> Shota Kawabuchi <shota.kawabuchi+GitHub@gmail.com> skwbc <shota.kawabuchi+GitHub@gmail.com> Siavash Eliasi <siavashserver@gmail.com> siavashserver <siavashserver@gmail.com> +Simon Gasse <simon.gasse@gmail.com> sgasse <sgasse@users.noreply.github.com> Søren Rasmussen <soren.rasmussen@alexandra.dk> sorenrasmussenai <47032123+sorenrasmussenai@users.noreply.github.com> Stefan van der Walt <stefanv@berkeley.edu> Stefan van der Walt <sjvdwalt@gmail.com> Stefan van der Walt <stefanv@berkeley.edu> Stefan van der Walt <stefan@sun.ac.za> @@ -229,6 +246,7 @@ Tony LaTorre <tlatorre@uchicago.edu> tlatorre <tlatorre@uchicago.edu> Travis Oliphant <travis@continuum.io> Travis E. Oliphant <teoliphant@gmail.com> Travis Oliphant <travis@continuum.io> Travis Oliphant <oliphant@enthought.com> Valentin Haenel <valentin@haenel.co> Valentin Haenel <valentin.haenel@gmx.de> +Rakesh Vasudevan <rakesh.nvasudev@gmail.com> vrakesh <rakesh.nvasudev@gmail.com> Vrinda Narayan <talk2vrinda@gmail.com> vrindaaa <48102157+vrindaaa@users.noreply.github.com> Warren Weckesser <warren.weckesser@enthought.com> Warren Weckesser <warren.weckesser@gmail.com> Weitang Li <liwt31@163.com> wtli@Dirac <liwt31@163.com> @@ -238,6 +256,7 @@ Wim Glenn <wim.glenn@melbourneit.com.au> wim glenn <wim.glenn@melbourneit.com.au Wojtek Ruszczewski <git@wr.waw.pl> wrwrwr <git@wr.waw.pl> Yuji Kanagawa <yuji.kngw.80s.revive@gmail.com> kngwyu <yuji.kngw.80s.revive@gmail.com> Yury Kirienko <yury.kirienko@gmail.com> kirienko <yury.kirienko@gmail.com> +Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com> Zac-HD <zac.hatfield.dodds@gmail.com> Zixu Zhao <zixu.zhao.tireless@gmail.com> ZZhaoTireless <zixu.zhao.tireless@gmail.com> Ziyan Zhou <ziyan.zhou@mujin.co.jp> Ziyan <ziyan.zhou@mujin.co.jp> Zieji Pohz <poh.ziji@gmail.com> jpoh <poh.zijie@gmail.com> diff --git a/.travis.yml b/.travis.yml index 85f6127cd..cbe2a8043 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,14 +5,13 @@ group: travis_latest os: linux dist: bionic -# Travis whitelists the installable packages, additions can be requested -# https://github.com/travis-ci/apt-package-whitelist +# Travis allows these packages, additions can be requested +# https://github.com/travis-ci/apt-package-safelist addons: apt: packages: &common_packages - gfortran - libgfortran5 - - libgfortran3 - libatlas-base-dev # Speedup builds, particularly when USE_CHROOT=1 - eatmydata @@ -38,7 +37,8 @@ jobs: - python: 3.7 - python: 3.9-dev - - python: 3.6 + - python: 3.8 + dist: focal env: USE_DEBUG=1 addons: apt: diff --git a/INSTALL.rst.txt b/INSTALL.rst.txt index 2b9226751..1c33060a6 100644 --- a/INSTALL.rst.txt +++ b/INSTALL.rst.txt @@ -20,7 +20,7 @@ Building NumPy requires the following installed software: e.g., on Debian/Ubuntu one needs to install both `python3` and `python3-dev`. On Windows and macOS this is normally not an issue. -2) Cython >= 0.29.14 +2) Cython >= 0.29.21 3) pytest__ (optional) 1.15 or later diff --git a/MANIFEST.in b/MANIFEST.in index f710c92e6..c62bf9b8c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -15,6 +15,7 @@ include tox.ini include .coveragerc include test_requirements.txt recursive-include numpy/random *.pyx *.pxd *.pyx.in *.pxd.in +include numpy/py.typed include numpy/random/include/* include numpy/__init__.pxd # Add build support that should go in sdist, but not go in bdist/be installed @@ -1,4 +1,4 @@ -# <img alt="NumPy" src="https://cdn.rawgit.com/numpy/numpy/master/branding/icons/numpylogo.svg" height="60"> +# <img alt="NumPy" src="https://ghcdn.rawgit.org/numpy/numpy/master/branding/icons/primary/numpylogo.svg" height="60"> []( https://travis-ci.org/numpy/numpy) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ea2b414b0..51f30c263 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -195,16 +195,6 @@ stages: strategy: maxParallel: 6 matrix: - Python36-32bit-fast: - PYTHON_VERSION: '3.6' - PYTHON_ARCH: 'x86' - TEST_MODE: fast - BITS: 32 - Python37-32bit-fast: - PYTHON_VERSION: '3.7' - PYTHON_ARCH: 'x86' - TEST_MODE: fast - BITS: 32 Python38-32bit-fast: PYTHON_VERSION: '3.8' PYTHON_ARCH: 'x86' @@ -251,7 +241,8 @@ stages: fi displayName: 'add gcc 4.8' - script: | - python3 -m pip install --user --upgrade pip setuptools + # python3 has no setuptools, so install one to get us going + python3 -m pip install --user --upgrade pip setuptools!=49.2.0 python3 -m pip install --user -r test_requirements.txt CPPFLAGS='' CC=gcc-4.8 F77=gfortran-5 F90=gfortran-5 \ python3 runtests.py --debug-info --mode=full -- -rsx --junitxml=junit/test-results.xml diff --git a/azure-steps-windows.yml b/azure-steps-windows.yml index af6d88947..4c4e1d177 100644 --- a/azure-steps-windows.yml +++ b/azure-steps-windows.yml @@ -4,7 +4,7 @@ steps: versionSpec: $(PYTHON_VERSION) addToPath: true architecture: $(PYTHON_ARCH) -- script: python -m pip install --upgrade pip setuptools wheel +- script: python -m pip install --upgrade pip displayName: 'Install tools' - script: python -m pip install -r test_requirements.txt displayName: 'Install dependencies; some are optional to avoid test skips' @@ -14,7 +14,6 @@ steps: # to $PYTHON_EXE's directory since that is on a different drive which # mingw does not like. Instead copy it to a directory and set OPENBLAS, # since OPENBLAS will be picked up by the openblas discovery - python -m pip install $target = $(python tools/openblas_support.py) mkdir openblas echo Copying $target to openblas/openblas$env:OPENBLAS_SUFFIX.a diff --git a/benchmarks/benchmarks/bench_avx.py b/benchmarks/benchmarks/bench_avx.py index ff105811d..82866c170 100644 --- a/benchmarks/benchmarks/bench_avx.py +++ b/benchmarks/benchmarks/bench_avx.py @@ -39,6 +39,19 @@ class AVX_UFunc(Benchmark): def time_ufunc(self, ufuncname, stride, dtype): self.f(self.arr[::stride]) +class AVX_UFunc_log(Benchmark): + params = [stride, dtype] + param_names = ['stride', 'dtype'] + timeout = 10 + + def setup(self, stride, dtype): + np.seterr(all='ignore') + N = 10000 + self.arr = np.array(np.random.random_sample(stride*N), dtype=dtype) + + def time_log(self, stride, dtype): + np.log(self.arr[::stride]) + avx_bfuncs = ['maximum', 'minimum'] diff --git a/benchmarks/benchmarks/bench_core.py b/benchmarks/benchmarks/bench_core.py index 060d0f7db..0c2a18c15 100644 --- a/benchmarks/benchmarks/bench_core.py +++ b/benchmarks/benchmarks/bench_core.py @@ -7,9 +7,13 @@ class Core(Benchmark): def setup(self): self.l100 = range(100) self.l50 = range(50) + self.float_l1000 = [float(i) for i in range(1000)] + self.float64_l1000 = [np.float64(i) for i in range(1000)] + self.int_l1000 = list(range(1000)) self.l = [np.arange(1000), np.arange(1000)] self.l_view = [memoryview(a) for a in self.l] self.l10x10 = np.ones((10, 10)) + self.float64_dtype = np.dtype(np.float64) def time_array_1(self): np.array(1) @@ -23,6 +27,18 @@ class Core(Benchmark): def time_array_l100(self): np.array(self.l100) + def time_array_float_l1000(self): + np.array(self.float_l1000) + + def time_array_float_l1000_dtype(self): + np.array(self.float_l1000, dtype=self.float64_dtype) + + def time_array_float64_l1000(self): + np.array(self.float64_l1000) + + def time_array_int_l1000(self): + np.array(self.int_l1000) + def time_array_l(self): np.array(self.l) diff --git a/benchmarks/benchmarks/bench_indexing.py b/benchmarks/benchmarks/bench_indexing.py index 9ee0d1fb5..3206392ea 100644 --- a/benchmarks/benchmarks/bench_indexing.py +++ b/benchmarks/benchmarks/bench_indexing.py @@ -31,6 +31,36 @@ class Indexing(Benchmark): self.func() +class ScalarIndexing(Benchmark): + params = [[0, 1, 2]] + param_names = ["ndim"] + + def setup(self, ndim): + self.array = np.ones((5,) * ndim) + + def time_index(self, ndim): + # time indexing. + arr = self.array + indx = (1,) * ndim + for i in range(100): + arr[indx] + + def time_assign(self, ndim): + # time assignment from a python scalar + arr = self.array + indx = (1,) * ndim + for i in range(100): + arr[indx] = 5. + + def time_assign_cast(self, ndim): + # time an assignment which may use a cast operation + arr = self.array + indx = (1,) * ndim + val = np.int16(43) + for i in range(100): + arr[indx] = val + + class IndexingSeparate(Benchmark): def setup(self): self.tmp_dir = mkdtemp() diff --git a/benchmarks/benchmarks/bench_linalg.py b/benchmarks/benchmarks/bench_linalg.py index 3abbe3670..dc2849d58 100644 --- a/benchmarks/benchmarks/bench_linalg.py +++ b/benchmarks/benchmarks/bench_linalg.py @@ -105,3 +105,29 @@ class Lstsq(Benchmark): def time_numpy_linalg_lstsq_a__b_float64(self): np.linalg.lstsq(self.a, self.b, rcond=-1) + +class Einsum(Benchmark): + param_names = ['dtype'] + params = [[np.float64]] + def setup(self, dtype): + self.a = np.arange(2900, dtype=dtype) + self.b = np.arange(3000, dtype=dtype) + self.c = np.arange(24000, dtype=dtype).reshape(20, 30, 40) + self.c1 = np.arange(1200, dtype=dtype).reshape(30, 40) + self.d = np.arange(10000, dtype=dtype).reshape(10,100,10) + + #outer(a,b): trigger sum_of_products_contig_stride0_outcontig_two + def time_einsum_outer(self, dtype): + np.einsum("i,j", self.a, self.b, optimize=True) + + # multiply(a, b):trigger sum_of_products_contig_two + def time_einsum_multiply(self, dtype): + np.einsum("..., ...", self.c1, self.c , optimize=True) + + # sum and multiply:trigger sum_of_products_contig_stride0_outstride0_two + def time_einsum_sum_mul(self, dtype): + np.einsum(",i...->", 300, self.d, optimize=True) + + # sum and multiply:trigger sum_of_products_stride0_contig_outstride0_two + def time_einsum_sum_mul2(self, dtype): + np.einsum("i...,->", self.d, 300, optimize=True)
\ No newline at end of file diff --git a/benchmarks/benchmarks/common.py b/benchmarks/benchmarks/common.py index 3fd81a164..b65cc5fd2 100644 --- a/benchmarks/benchmarks/common.py +++ b/benchmarks/benchmarks/common.py @@ -111,4 +111,4 @@ def get_indexes_rand_(): class Benchmark: - goal_time = 0.25 + pass diff --git a/branding/icons/logoguidelines.md b/branding/icons/logoguidelines.md new file mode 100644 index 000000000..c674a9b37 --- /dev/null +++ b/branding/icons/logoguidelines.md @@ -0,0 +1,18 @@ +# NumPy Logo Guidelines +These guidelines are meant to help keep the NumPy logo consistent and recognizable across all its uses. They also provide a common language for referring to the logos and their components. + +The primary logo is the horizontal option (logomark and text next to each other) and the secondary logo is the stacked version (logomark over text). I’ve also provided the logomark on its own (meaning it doesn’t have text). When in doubt, it’s preferable to use primary or secondary options over the logomark alone. + +## Color +The full color options are a combo of Maximum Blue (#4DABCF) and Han Blue (#4D77CF), while light options are white (#FFFFFF) and dark options Warm Black (#013243). + +Whenever possible, use the full color logos. One color logos (light or dark) are to be used when full color will not have enough contrast, usually when logos must be on colored backgrounds. + +## Minimum Size +Please do not make the primary logo smaller than 50px wide, secondary logo smaller than 35px wide, or logomark smaller than 20px wide. + +## Logo Integrity +A few other notes to keep in mind when using the logo: +- Make sure to scale the logo proportionally. +- Maintain a good amount of space around the logo. Don’t let it overlap with text, images, or other elements. +- Do not try and recreate or modify the logo. For example, do not use the logomark and then try to write NumPy in another font.
\ No newline at end of file diff --git a/branding/icons/logomark/numpylogoicon.png b/branding/icons/logomark/numpylogoicon.png Binary files differnew file mode 100644 index 000000000..4d663fe0a --- /dev/null +++ b/branding/icons/logomark/numpylogoicon.png diff --git a/branding/icons/logomark/numpylogoicon.svg b/branding/icons/logomark/numpylogoicon.svg new file mode 100644 index 000000000..4fef2a9c8 --- /dev/null +++ b/branding/icons/logomark/numpylogoicon.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><defs><style>.cls-1{fill:#4dabcf;}.cls-2{fill:#4d77cf;}</style></defs><g id="Layer_1" data-name="Layer 1"><polygon class="cls-1" points="220.93 127.14 151.77 92.23 75.87 130.11 146.9 165.78 220.93 127.14"/><polygon class="cls-1" points="252.63 143.14 325.14 179.74 249.91 217.52 178.77 181.79 252.63 143.14"/><polygon class="cls-1" points="349.47 92.76 423.96 130.11 357.34 163.57 284.68 126.92 349.47 92.76"/><polygon class="cls-1" points="317.41 76.67 250.35 43.05 184.01 76.15 253.11 111 317.41 76.67"/><polygon class="cls-1" points="264.98 365.44 264.98 456.95 346.22 416.41 346.13 324.86 264.98 365.44"/><polygon class="cls-1" points="346.1 292.91 346.01 202.32 264.98 242.6 264.98 333.22 346.1 292.91"/><polygon class="cls-1" points="443.63 275.93 443.63 367.8 374.34 402.38 374.29 310.93 443.63 275.93"/><polygon class="cls-1" points="443.63 243.81 443.63 153.79 374.21 188.3 374.27 279.07 443.63 243.81"/><path class="cls-2" d="M236.3,242.6l-54.72-27.51V334s-66.92-142.39-73.12-155.18c-.8-1.65-4.09-3.46-4.93-3.9-12-6.3-47.16-24.11-47.16-24.11V360.89l48.64,26V277.08s66.21,127.23,66.88,128.62,7.32,14.8,14.42,19.51c9.46,6.26,50,30.64,50,30.64Z"/></g></svg>
\ No newline at end of file diff --git a/branding/icons/logomark/numpylogoicondark.png b/branding/icons/logomark/numpylogoicondark.png Binary files differnew file mode 100644 index 000000000..eea3f3288 --- /dev/null +++ b/branding/icons/logomark/numpylogoicondark.png diff --git a/branding/icons/logomark/numpylogoiconlight.png b/branding/icons/logomark/numpylogoiconlight.png Binary files differnew file mode 100644 index 000000000..a81b175a6 --- /dev/null +++ b/branding/icons/logomark/numpylogoiconlight.png diff --git a/branding/icons/numpylogo.svg b/branding/icons/numpylogo.svg deleted file mode 100644 index bd9b834da..000000000 --- a/branding/icons/numpylogo.svg +++ /dev/null @@ -1,7109 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 12.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 51448) --> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ - <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/"> - <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/"> - <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/"> - <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/"> - <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/"> - <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/"> - <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/"> - <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/"> - <!ENTITY ns_svg "http://www.w3.org/2000/svg"> - <!ENTITY ns_xlink "http://www.w3.org/1999/xlink"> -]> -<svg version="1.1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" - width="774.692" height="307.15" viewBox="0 0 774.692 307.15" overflow="visible" enable-background="new 0 0 774.692 307.15" - xml:space="preserve"> -<switch> - <foreignObject requiredExtensions="&ns_ai;" x="0" y="0" width="1" height="1"> - <i:pgfRef xlink:href="#adobe_illustrator_pgf"> - </i:pgfRef> - </foreignObject> - <g i:extraneous="self"> - <g id="Layer_1"> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="132.807,162.121 133.714,207.379 89.084,199.51 88.177,154.251 "/> - </g> - <g> - <polygon fill="#7A88CC" points="65.681,217.759 65.672,217.294 88.201,199.81 88.21,200.274 "/> - </g> - <g> - <polygon fill="#7684CA" points="66.131,217.839 65.681,217.759 88.21,200.274 88.661,200.354 "/> - </g> - <g> - <path fill="#6272C3" d="M133.23,161.276l0.45,0.079l0.009,0.466l0.926,46.172l0.009,0.466l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.465l-0.925-46.173l-0.009-0.466l0.45,0.08L133.23,161.276z M133.714,207.379l-0.907-45.258l-44.63-7.87 - l0.907,45.259L133.714,207.379"/> - </g> - <g> - <polygon fill="#7A88CC" points="65.672,217.294 64.746,171.121 87.276,153.637 88.201,199.81 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.746,171.121 64.737,170.655 87.267,153.171 87.276,153.637 "/> - </g> - <g> - <polygon fill="#7A88CC" points="65.647,171.735 88.177,154.251 89.084,199.51 66.554,216.994 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="66.554,216.994 65.647,171.735 88.177,154.251 89.084,199.51 "/> - </g> - <g> - <polygon fill="#7684CA" points="64.737,170.655 87.267,153.171 87.717,153.25 65.187,170.735 "/> - </g> - <g> - <polygon fill="#7684CA" points="111.645,225.864 66.131,217.839 88.661,200.354 134.175,208.38 "/> - </g> - <g> - <polygon fill="#769AC7" points="111.185,224.863 66.554,216.994 89.084,199.51 133.714,207.379 "/> - </g> - <g> - <polygon fill="#7684CA" points="66.554,216.994 89.084,199.51 133.714,207.379 111.185,224.863 "/> - </g> - <g> - <polygon fill="#7684CA" points="112.095,225.943 111.645,225.864 134.175,208.38 134.625,208.459 "/> - </g> - <g> - <polygon fill="#7A88CC" points="112.085,225.478 134.615,207.993 134.625,208.459 112.095,225.943 "/> - </g> - <g> - <polygon fill="#628CBE" points="110.277,179.604 111.185,224.863 66.554,216.994 65.647,171.735 "/> - </g> - <g> - <polygon fill="#769AC7" points="65.647,171.735 88.177,154.251 132.807,162.121 110.277,179.604 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.277,179.604 65.647,171.735 88.177,154.251 132.807,162.121 "/> - </g> - <g> - <polygon fill="#7A88CC" points="111.185,224.863 110.277,179.604 132.807,162.121 133.714,207.379 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="110.277,179.604 132.807,162.121 133.714,207.379 111.185,224.863 "/> - </g> - <g> - <polygon fill="#7684CA" points="65.187,170.735 87.717,153.25 133.23,161.276 110.701,178.76 "/> - </g> - <g> - <polygon fill="#7A88CC" points="111.16,179.305 133.689,161.821 134.615,207.993 112.085,225.478 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.701,178.76 133.23,161.276 133.68,161.355 111.15,178.839 "/> - </g> - <g> - <polygon fill="#7A88CC" points="111.15,178.839 133.68,161.355 133.689,161.821 111.16,179.305 "/> - </g> - <g> - <path fill="#6272C3" d="M110.701,178.76l0.45,0.079l0.01,0.466l0.925,46.172l0.009,0.466l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.465l-0.926-46.173l-0.009-0.466l0.45,0.08L110.701,178.76z M111.185,224.863l-0.907-45.259l-44.63-7.869 - l0.907,45.259L111.185,224.863"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M286.114,189.068l0.45,0.079l0.009,0.466l0.926,46.181l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.466l0.45,0.08L286.114,189.068z M286.598,235.171l-0.907-45.25l-44.63-7.87 - l0.907,45.25L286.598,235.171"/> - </g> - <g> - <polygon fill="#628CBE" points="285.69,189.921 286.598,235.171 241.967,227.302 241.06,182.051 "/> - </g> - <g> - <polygon fill="#7A88CC" points="218.564,245.552 218.555,245.094 241.085,227.609 241.094,228.067 "/> - </g> - <g> - <polygon fill="#7684CA" points="219.014,245.631 218.564,245.552 241.094,228.067 241.544,228.146 "/> - </g> - <g> - <polygon fill="#7A88CC" points="218.555,245.094 217.629,198.913 240.159,181.429 241.085,227.609 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.629,198.913 217.62,198.447 240.15,180.963 240.159,181.429 "/> - </g> - <g> - <polygon fill="#7A88CC" points="218.53,199.536 241.06,182.051 241.967,227.302 219.438,244.786 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="219.438,244.786 218.53,199.536 241.06,182.051 241.967,227.302 "/> - </g> - <g> - <polygon fill="#7684CA" points="217.62,198.447 240.15,180.963 240.6,181.042 218.07,198.526 "/> - </g> - <g> - <polygon fill="#7684CA" points="264.528,253.656 219.014,245.631 241.544,228.146 287.058,236.172 "/> - </g> - <g> - <polygon fill="#769AC7" points="264.068,252.655 219.438,244.786 241.967,227.302 286.598,235.171 "/> - </g> - <g> - <polygon fill="#7684CA" points="219.438,244.786 241.967,227.302 286.598,235.171 264.068,252.655 "/> - </g> - <g> - <polygon fill="#7684CA" points="264.978,253.735 264.528,253.656 287.058,236.172 287.508,236.251 "/> - </g> - <g> - <polygon fill="#7A88CC" points="264.969,253.278 287.499,235.794 287.508,236.251 264.978,253.735 "/> - </g> - <g> - <polygon fill="#628CBE" points="263.161,207.405 264.068,252.655 219.438,244.786 218.53,199.536 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.161,207.405 218.53,199.536 241.06,182.051 285.69,189.921 "/> - </g> - <g> - <polygon fill="#769AC7" points="218.53,199.536 241.06,182.051 285.69,189.921 263.161,207.405 "/> - </g> - <g> - <polygon fill="#7A88CC" points="264.068,252.655 263.161,207.405 285.69,189.921 286.598,235.171 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="263.161,207.405 285.69,189.921 286.598,235.171 264.068,252.655 "/> - </g> - <g> - <polygon fill="#7684CA" points="218.07,198.526 240.6,181.042 286.114,189.068 263.584,206.552 "/> - </g> - <g> - <polygon fill="#7A88CC" points="264.043,207.097 286.573,189.613 287.499,235.794 264.969,253.278 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.584,206.552 286.114,189.068 286.563,189.147 264.034,206.631 "/> - </g> - <g> - <polygon fill="#7A88CC" points="264.034,206.631 286.563,189.147 286.573,189.613 264.043,207.097 "/> - </g> - <g> - <path fill="#6272C3" d="M263.584,206.552l0.45,0.079l0.009,0.466l0.926,46.182l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.466l0.45,0.079L263.584,206.552z M264.068,252.655l-0.907-45.25 - l-44.631-7.87l0.907,45.25L264.068,252.655"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="234.57,180.831 235.478,226.081 190.855,218.214 189.948,172.963 "/> - </g> - <g> - <polygon fill="#7A88CC" points="167.444,236.462 167.435,236.005 189.965,218.521 189.974,218.978 "/> - </g> - <g> - <polygon fill="#7684CA" points="167.894,236.541 167.444,236.462 189.974,218.978 190.423,219.057 "/> - </g> - <g> - <path fill="#6272C3" d="M234.994,179.986l0.45,0.079l0.009,0.458l0.926,46.181l0.009,0.457l-0.45-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.45,0.079L234.994,179.986z M235.478,226.081l-0.907-45.25 - l-44.623-7.868l0.907,45.251L235.478,226.081"/> - </g> - <g> - <polygon fill="#7A88CC" points="167.435,236.005 166.509,189.823 189.039,172.339 189.965,218.521 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.509,189.823 166.5,189.366 189.03,171.882 189.039,172.339 "/> - </g> - <g> - <polygon fill="#7A88CC" points="167.418,190.447 189.948,172.963 190.855,218.214 168.325,235.697 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="168.325,235.697 167.418,190.447 189.948,172.963 190.855,218.214 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.5,189.366 189.03,171.882 189.479,171.961 166.95,189.445 "/> - </g> - <g> - <polygon fill="#7684CA" points="213.408,244.566 167.894,236.541 190.423,219.057 235.938,227.082 "/> - </g> - <g> - <polygon fill="#769AC7" points="212.948,243.565 168.325,235.697 190.855,218.214 235.478,226.081 "/> - </g> - <g> - <polygon fill="#7684CA" points="168.325,235.697 190.855,218.214 235.478,226.081 212.948,243.565 "/> - </g> - <g> - <polygon fill="#7684CA" points="213.858,244.646 213.408,244.566 235.938,227.082 236.388,227.161 "/> - </g> - <g> - <polygon fill="#7A88CC" points="213.849,244.188 236.378,226.704 236.388,227.161 213.858,244.646 "/> - </g> - <g> - <polygon fill="#769AC7" points="167.418,190.447 189.948,172.963 234.57,180.831 212.041,198.315 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.041,198.315 167.418,190.447 189.948,172.963 234.57,180.831 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.948,243.565 212.041,198.315 234.57,180.831 235.478,226.081 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="212.041,198.315 234.57,180.831 235.478,226.081 212.948,243.565 "/> - </g> - <g> - <path fill="#6272C3" d="M212.464,197.471l0.45,0.079l0.009,0.458l0.926,46.181l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.458l0.45,0.08L212.464,197.471z M212.948,243.565l-0.907-45.25 - l-44.623-7.868l0.907,45.25L212.948,243.565"/> - </g> - <g> - <polygon fill="#628CBE" points="167.418,190.447 168.325,235.697 212.948,243.565 212.041,198.315 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.95,189.445 189.479,171.961 234.994,179.986 212.464,197.471 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.923,198.007 235.453,180.523 236.378,226.704 213.849,244.188 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.464,197.471 234.994,179.986 235.443,180.065 212.914,197.55 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.914,197.55 235.443,180.065 235.453,180.523 212.923,198.007 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="183.189,171.48 184.097,216.73 139.466,208.861 138.559,163.61 "/> - </g> - <g> - <polygon fill="#7A88CC" points="116.063,227.11 116.054,226.653 138.584,209.169 138.593,209.626 "/> - </g> - <g> - <polygon fill="#7684CA" points="116.513,227.19 116.063,227.11 138.593,209.626 139.043,209.706 "/> - </g> - <g> - <path fill="#6272C3" d="M183.613,170.627l0.45,0.079l0.009,0.465l0.926,46.182l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.457l-0.926-46.182l-0.009-0.465l0.45,0.08L183.613,170.627z M184.097,216.73l-0.907-45.25l-44.63-7.87 - l0.907,45.251L184.097,216.73"/> - </g> - <g> - <polygon fill="#7A88CC" points="116.054,226.653 115.128,180.472 137.658,162.987 138.584,209.169 "/> - </g> - <g> - <polygon fill="#7A88CC" points="115.128,180.472 115.119,180.006 137.649,162.522 137.658,162.987 "/> - </g> - <g> - <polygon fill="#7A88CC" points="116.029,181.095 138.559,163.61 139.466,208.861 116.937,226.346 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="116.937,226.346 116.029,181.095 138.559,163.61 139.466,208.861 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.119,180.006 137.649,162.522 138.099,162.602 115.569,180.086 "/> - </g> - <g> - <polygon fill="#7684CA" points="162.027,235.216 116.513,227.19 139.043,209.706 184.557,217.731 "/> - </g> - <g> - <polygon fill="#7684CA" points="116.937,226.346 139.466,208.861 184.097,216.73 161.567,234.215 "/> - </g> - <g> - <polygon fill="#769AC7" points="161.567,234.215 116.937,226.346 139.466,208.861 184.097,216.73 "/> - </g> - <g> - <polygon fill="#7684CA" points="162.477,235.295 162.027,235.216 184.557,217.731 185.007,217.811 "/> - </g> - <g> - <polygon fill="#7A88CC" points="162.468,234.838 184.998,217.354 185.007,217.811 162.477,235.295 "/> - </g> - <g> - <polygon fill="#628CBE" points="160.66,188.964 161.567,234.215 116.937,226.346 116.029,181.095 "/> - </g> - <g> - <polygon fill="#7684CA" points="160.66,188.964 116.029,181.095 138.559,163.61 183.189,171.48 "/> - </g> - <g> - <polygon fill="#769AC7" points="116.029,181.095 138.559,163.61 183.189,171.48 160.66,188.964 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.567,234.215 160.66,188.964 183.189,171.48 184.097,216.73 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="160.66,188.964 183.189,171.48 184.097,216.73 161.567,234.215 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.569,180.086 138.099,162.602 183.613,170.627 161.083,188.111 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.542,188.656 184.072,171.171 184.998,217.354 162.468,234.838 "/> - </g> - <g> - <polygon fill="#7684CA" points="161.083,188.111 183.613,170.627 184.063,170.706 161.533,188.19 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.533,188.19 184.063,170.706 184.072,171.171 161.542,188.656 "/> - </g> - <g> - <path fill="#6272C3" d="M161.083,188.111l0.45,0.079l0.009,0.465l0.926,46.182l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.457l-0.926-46.182l-0.009-0.465l0.45,0.08L161.083,188.111z M161.567,234.215l-0.907-45.25l-44.631-7.87 - l0.907,45.251L161.567,234.215"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="131.809,111.126 132.716,156.376 88.085,148.507 87.178,103.257 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.682,166.757 64.673,166.3 87.202,148.815 87.211,149.272 "/> - </g> - <g> - <polygon fill="#7684CA" points="65.132,166.836 64.682,166.757 87.211,149.272 87.662,149.352 "/> - </g> - <g> - <path fill="#6272C3" d="M132.231,110.282l0.45,0.079l0.009,0.457L133.616,157l0.009,0.457l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.45,0.08L132.231,110.282z M132.716,156.376l-0.907-45.25 - l-44.631-7.87l0.907,45.25L132.716,156.376"/> - </g> - <g> - <polygon fill="#7A88CC" points="64.673,166.3 63.747,120.118 86.276,102.634 87.202,148.815 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.747,120.118 63.738,119.661 86.268,102.177 86.276,102.634 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.648,120.741 87.178,103.257 88.085,148.507 65.555,165.991 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="65.555,165.991 64.648,120.741 87.178,103.257 88.085,148.507 "/> - </g> - <g> - <polygon fill="#7684CA" points="63.738,119.661 86.268,102.177 86.718,102.256 64.188,119.741 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.646,174.861 65.132,166.836 87.662,149.352 133.176,157.377 "/> - </g> - <g> - <polygon fill="#769AC7" points="110.186,173.861 65.555,165.991 88.085,148.507 132.716,156.376 "/> - </g> - <g> - <polygon fill="#7684CA" points="65.555,165.991 88.085,148.507 132.716,156.376 110.186,173.861 "/> - </g> - <g> - <polygon fill="#7684CA" points="111.096,174.941 110.646,174.861 133.176,157.377 133.625,157.457 "/> - </g> - <g> - <polygon fill="#7A88CC" points="111.087,174.484 133.616,157 133.625,157.457 111.096,174.941 "/> - </g> - <g> - <polygon fill="#628CBE" points="109.279,128.611 110.186,173.861 65.555,165.991 64.648,120.741 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.279,128.611 64.648,120.741 87.178,103.257 131.809,111.126 "/> - </g> - <g> - <polygon fill="#769AC7" points="64.648,120.741 87.178,103.257 131.809,111.126 109.279,128.611 "/> - </g> - <g> - <polygon fill="#7A88CC" points="110.186,173.861 109.279,128.611 131.809,111.126 132.716,156.376 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="109.279,128.611 131.809,111.126 132.716,156.376 110.186,173.861 "/> - </g> - <g> - <polygon fill="#7684CA" points="64.188,119.741 86.718,102.256 132.231,110.282 109.702,127.766 "/> - </g> - <g> - <polygon fill="#7A88CC" points="110.161,128.302 132.69,110.818 133.616,157 111.087,174.484 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.702,127.766 132.231,110.282 132.681,110.361 110.152,127.845 "/> - </g> - <g> - <polygon fill="#7A88CC" points="110.152,127.845 132.681,110.361 132.69,110.818 110.161,128.302 "/> - </g> - <g> - <path fill="#6272C3" d="M109.702,127.766l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.45,0.08L109.702,127.766z M110.186,173.861l-0.907-45.25 - l-44.631-7.87l0.907,45.25L110.186,173.861"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M285.115,138.074l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.465l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.465l-0.926-46.182l-0.009-0.457l0.45,0.08L285.115,138.074z M285.599,184.169l-0.907-45.25l-44.63-7.87 - l0.907,45.25L285.599,184.169"/> - </g> - <g> - <polygon fill="#628CBE" points="284.691,138.919 285.599,184.169 240.968,176.299 240.061,131.049 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.565,194.558 217.556,194.092 240.085,176.608 240.095,177.073 "/> - </g> - <g> - <polygon fill="#7684CA" points="218.015,194.637 217.565,194.558 240.095,177.073 240.545,177.152 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.556,194.092 216.63,147.911 239.16,130.426 240.085,176.608 "/> - </g> - <g> - <polygon fill="#7A88CC" points="216.63,147.911 216.621,147.454 239.15,129.969 239.16,130.426 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.531,148.534 240.061,131.049 240.968,176.299 218.438,193.784 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="218.438,193.784 217.531,148.534 240.061,131.049 240.968,176.299 "/> - </g> - <g> - <polygon fill="#7684CA" points="216.621,147.454 239.15,129.969 239.601,130.049 217.071,147.533 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.529,202.662 218.015,194.637 240.545,177.152 286.059,185.178 "/> - </g> - <g> - <polygon fill="#769AC7" points="263.069,201.653 218.438,193.784 240.968,176.299 285.599,184.169 "/> - </g> - <g> - <polygon fill="#7684CA" points="218.438,193.784 240.968,176.299 285.599,184.169 263.069,201.653 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.979,202.741 263.529,202.662 286.059,185.178 286.509,185.257 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.97,202.276 286.5,184.792 286.509,185.257 263.979,202.741 "/> - </g> - <g> - <polygon fill="#769AC7" points="217.531,148.534 240.061,131.049 284.691,138.919 262.162,156.403 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.162,156.403 217.531,148.534 240.061,131.049 284.691,138.919 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.069,201.653 262.162,156.403 284.691,138.919 285.599,184.169 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="262.162,156.403 284.691,138.919 285.599,184.169 263.069,201.653 "/> - </g> - <g> - <path fill="#6272C3" d="M262.585,155.559l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.465l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.465l-0.926-46.182l-0.009-0.457l0.45,0.08L262.585,155.559z M263.069,201.653l-0.907-45.25l-44.63-7.87 - l0.907,45.25L263.069,201.653"/> - </g> - <g> - <path fill="#628CBE" d="M217.531,148.534l0.907,45.25l44.63,7.87l-0.907-45.25L217.531,148.534z M217.531,148.534 - L217.531,148.534L217.531,148.534L217.531,148.534z"/> - </g> - <g> - <polygon fill="#7684CA" points="217.071,147.533 239.601,130.049 285.115,138.074 262.585,155.559 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.044,156.095 285.574,138.61 286.5,184.792 263.97,202.276 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.585,155.559 285.115,138.074 285.564,138.153 263.035,155.638 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.035,155.638 285.564,138.153 285.574,138.61 263.044,156.095 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M233.995,128.984l0.45,0.079l0.009,0.465l0.926,46.173l0.009,0.465l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.465l-0.925-46.173l-0.01-0.465l0.45,0.08L233.995,128.984z M234.479,175.087l-0.907-45.258l-44.631-7.87 - l0.908,45.258L234.479,175.087"/> - </g> - <g> - <polygon fill="#7A88CC" points="166.445,185.467 166.436,185.002 188.965,167.518 188.975,167.983 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.895,185.546 166.445,185.467 188.975,167.983 189.425,168.063 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.409,193.572 166.895,185.546 189.425,168.063 234.939,176.088 "/> - </g> - <g> - <polygon fill="#769AC7" points="211.949,192.571 167.318,184.702 189.848,167.218 234.479,175.087 "/> - </g> - <g> - <polygon fill="#7684CA" points="167.318,184.702 189.848,167.217 234.479,175.087 211.949,192.571 "/> - </g> - <g> - <polygon fill="#628CBE" points="233.571,129.829 234.479,175.087 189.848,167.218 188.941,121.959 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.436,185.002 165.51,138.829 188.04,121.344 188.965,167.518 "/> - </g> - <g> - <polygon fill="#7A88CC" points="165.51,138.829 165.501,138.363 188.03,120.879 188.04,121.344 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.411,139.443 188.94,121.959 189.848,167.217 167.318,184.702 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="167.318,184.702 166.411,139.443 188.941,121.959 189.848,167.218 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.501,138.363 188.03,120.879 188.48,120.958 165.951,138.442 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.859,193.651 212.409,193.572 234.939,176.088 235.389,176.167 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.85,193.186 235.379,175.702 235.389,176.167 212.859,193.651 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.042,147.313 166.411,139.443 188.94,121.959 233.571,129.829 "/> - </g> - <g> - <polygon fill="#769AC7" points="166.411,139.443 188.941,121.959 233.571,129.829 211.042,147.313 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.949,192.571 211.042,147.313 233.571,129.829 234.479,175.087 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="211.042,147.313 233.571,129.829 234.479,175.087 211.949,192.571 "/> - </g> - <g> - <path fill="#6272C3" d="M211.465,146.468l0.45,0.08l0.009,0.465l0.925,46.173l0.009,0.465l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.465l-0.926-46.173l-0.009-0.465l0.45,0.079L211.465,146.468z M211.949,192.571l-0.907-45.258 - l-44.63-7.87l0.907,45.258L211.949,192.571"/> - </g> - <g> - <path fill="#628CBE" d="M211.042,147.313l-44.63-7.87l0.907,45.258l44.63,7.87L211.042,147.313z M166.411,139.443 - L166.411,139.443L166.411,139.443L166.411,139.443z"/> - </g> - <g> - <polygon fill="#7684CA" points="165.951,138.442 188.48,120.958 233.995,128.984 211.465,146.468 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.924,147.013 234.454,129.528 235.379,175.702 212.85,193.186 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.465,146.468 233.995,128.984 234.444,129.063 211.915,146.547 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.915,146.547 234.444,129.063 234.454,129.528 211.924,147.013 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M182.614,119.633l0.45,0.079l0.009,0.457l0.926,46.181l0.009,0.458l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.457l0.45,0.079L182.614,119.633z M183.098,165.728l-0.907-45.25 - l-44.63-7.87l0.907,45.25L183.098,165.728"/> - </g> - <g> - <polygon fill="#628CBE" points="182.19,120.477 183.098,165.728 138.467,157.858 137.56,112.607 "/> - </g> - <g> - <polygon fill="#7A88CC" points="115.064,176.108 115.055,175.65 137.585,158.166 137.594,158.624 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.514,176.187 115.064,176.108 137.594,158.624 138.043,158.703 "/> - </g> - <g> - <polygon fill="#7A88CC" points="115.055,175.65 114.129,129.469 136.659,111.985 137.585,158.166 "/> - </g> - <g> - <polygon fill="#7A88CC" points="114.129,129.469 114.12,129.012 136.65,111.528 136.659,111.985 "/> - </g> - <g> - <polygon fill="#7A88CC" points="115.03,130.092 137.56,112.607 138.467,157.858 115.938,175.342 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="115.938,175.342 115.03,130.092 137.56,112.607 138.467,157.858 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.12,129.012 136.65,111.528 137.1,111.607 114.57,129.092 "/> - </g> - <g> - <polygon fill="#7684CA" points="161.028,184.212 115.514,176.187 138.043,158.703 183.558,166.728 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.938,175.342 138.467,157.858 183.098,165.728 160.568,183.212 "/> - </g> - <g> - <polygon fill="#769AC7" points="160.568,183.212 115.938,175.342 138.467,157.858 183.098,165.728 "/> - </g> - <g> - <polygon fill="#7684CA" points="161.478,184.292 161.028,184.212 183.558,166.728 184.008,166.808 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.469,183.834 183.999,166.35 184.008,166.808 161.478,184.292 "/> - </g> - <g> - <polygon fill="#628CBE" points="159.661,137.961 160.568,183.212 115.938,175.342 115.03,130.092 "/> - </g> - <g> - <polygon fill="#7684CA" points="159.661,137.961 115.03,130.092 137.56,112.607 182.19,120.477 "/> - </g> - <g> - <polygon fill="#769AC7" points="115.03,130.092 137.56,112.607 182.19,120.477 159.661,137.961 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="159.661,137.961 182.19,120.477 183.098,165.728 160.568,183.212 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.568,183.212 159.661,137.961 182.19,120.477 183.098,165.728 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.57,129.092 137.1,111.607 182.614,119.633 160.084,137.117 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.543,137.653 183.073,120.169 183.999,166.35 161.469,183.834 "/> - </g> - <g> - <polygon fill="#7684CA" points="160.084,137.117 182.614,119.633 183.063,119.712 160.534,137.196 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.534,137.196 183.063,119.712 183.073,120.169 160.543,137.653 "/> - </g> - <g> - <path fill="#6272C3" d="M160.084,137.117l0.45,0.079l0.009,0.457l0.926,46.181l0.009,0.458l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.457l0.45,0.08L160.084,137.117z M160.568,183.212l-0.907-45.25l-44.63-7.87 - l0.907,45.25L160.568,183.212"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M131.817,59.099l0.45,0.08l0.009,0.465l0.926,46.181l0.009,0.458l-0.45-0.08l-45.514-8.025l-0.45-0.08 - l-0.009-0.458l-0.926-46.181l-0.01-0.465l0.45,0.08L131.817,59.099z M132.302,105.202l-0.907-45.258l-44.631-7.87 - l0.907,45.258L132.302,105.202"/> - </g> - <g> - <polygon fill="#628CBE" points="131.395,59.944 132.302,105.202 87.671,97.333 86.764,52.074 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.269,115.583 64.259,115.125 86.789,97.641 86.798,98.098 "/> - </g> - <g> - <polygon fill="#7684CA" points="64.718,115.662 64.269,115.583 86.798,98.098 87.248,98.178 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.259,115.125 63.333,68.944 85.863,51.459 86.789,97.641 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.333,68.944 63.324,68.479 85.854,50.994 85.863,51.459 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.234,69.559 86.764,52.074 87.671,97.333 65.141,114.817 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="65.141,114.817 64.234,69.559 86.764,52.074 87.671,97.333 "/> - </g> - <g> - <polygon fill="#7684CA" points="63.324,68.479 85.854,50.994 86.304,51.074 63.774,68.558 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.232,123.687 64.718,115.662 87.248,98.178 132.762,106.203 "/> - </g> - <g> - <polygon fill="#7684CA" points="65.141,114.817 87.671,97.333 132.302,105.202 109.772,122.687 "/> - </g> - <g> - <polygon fill="#769AC7" points="109.772,122.687 65.141,114.817 87.671,97.333 132.302,105.202 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.682,123.767 110.232,123.687 132.762,106.203 133.212,106.282 "/> - </g> - <g> - <polygon fill="#7A88CC" points="110.673,123.309 133.203,105.825 133.212,106.282 110.682,123.767 "/> - </g> - <g> - <polygon fill="#7684CA" points="108.865,77.428 64.234,69.559 86.764,52.074 131.395,59.944 "/> - </g> - <g> - <polygon fill="#769AC7" points="64.234,69.559 86.764,52.074 131.395,59.944 108.865,77.428 "/> - </g> - <g> - <polygon fill="#7A88CC" points="109.772,122.687 108.865,77.428 131.395,59.944 132.302,105.202 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="108.865,77.428 131.395,59.944 132.302,105.202 109.772,122.687 "/> - </g> - <g> - <path fill="#6272C3" d="M109.288,76.583l0.45,0.08l0.009,0.465l0.926,46.181l0.009,0.458l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.465l0.45,0.079L109.288,76.583z M109.772,122.687l-0.907-45.259 - l-44.631-7.869l0.907,45.258L109.772,122.687"/> - </g> - <g> - <path fill="#628CBE" d="M64.234,69.559l0.907,45.258l44.631,7.87l-0.907-45.259L64.234,69.559z M64.234,69.559L64.234,69.559 - L64.234,69.559L64.234,69.559z"/> - </g> - <g> - <polygon fill="#7684CA" points="63.774,68.558 86.304,51.074 131.817,59.099 109.288,76.583 "/> - </g> - <g> - <polygon fill="#7A88CC" points="109.748,77.128 132.277,59.644 133.203,105.825 110.673,123.309 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.288,76.583 131.817,59.099 132.268,59.178 109.738,76.663 "/> - </g> - <g> - <polygon fill="#7A88CC" points="109.738,76.663 132.268,59.178 132.277,59.644 109.748,77.128 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M284.701,86.899l0.45,0.079l0.009,0.458l0.926,46.181l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.457l0.45,0.079L284.701,86.899z M285.185,132.995l-0.907-45.25l-44.63-7.87 - l0.907,45.25L285.185,132.995"/> - </g> - <g> - <polygon fill="#628CBE" points="284.277,87.744 285.185,132.995 240.554,125.125 239.647,79.875 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.151,143.375 217.142,142.917 239.672,125.433 239.681,125.891 "/> - </g> - <g> - <polygon fill="#7684CA" points="217.601,143.454 217.151,143.375 239.681,125.891 240.13,125.97 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.142,142.917 216.216,96.736 238.746,79.252 239.672,125.433 "/> - </g> - <g> - <polygon fill="#7A88CC" points="216.216,96.736 216.207,96.279 238.737,78.795 238.746,79.252 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.117,97.359 239.647,79.875 240.554,125.125 218.024,142.609 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="218.024,142.609 217.117,97.359 239.647,79.875 240.554,125.125 "/> - </g> - <g> - <polygon fill="#7684CA" points="216.207,96.279 238.737,78.795 239.187,78.874 216.657,96.358 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.115,151.479 217.601,143.454 240.13,125.97 285.645,133.995 "/> - </g> - <g> - <polygon fill="#7684CA" points="218.024,142.609 240.554,125.125 285.185,132.995 262.655,150.479 "/> - </g> - <g> - <polygon fill="#769AC7" points="262.655,150.479 218.024,142.609 240.554,125.125 285.185,132.995 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.565,151.559 263.115,151.479 285.645,133.995 286.095,134.074 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.556,151.102 286.085,133.617 286.095,134.074 263.565,151.559 "/> - </g> - <g> - <polygon fill="#7684CA" points="261.748,105.229 217.117,97.359 239.647,79.875 284.277,87.744 "/> - </g> - <g> - <polygon fill="#628CBE" points="261.748,105.229 262.655,150.479 218.024,142.609 217.117,97.359 "/> - </g> - <g> - <polygon fill="#769AC7" points="217.117,97.359 239.647,79.875 284.277,87.744 261.748,105.229 "/> - </g> - <g> - <polygon fill="#7A88CC" points="262.655,150.479 261.748,105.229 284.277,87.744 285.185,132.995 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="261.748,105.229 284.277,87.744 285.185,132.995 262.655,150.479 "/> - </g> - <g> - <polygon fill="#7684CA" points="216.657,96.358 239.187,78.874 284.701,86.899 262.171,104.384 "/> - </g> - <g> - <polygon fill="#7A88CC" points="262.63,104.92 285.16,87.436 286.085,133.617 263.556,151.102 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.171,104.384 284.701,86.899 285.15,86.979 262.621,104.463 "/> - </g> - <g> - <polygon fill="#7A88CC" points="262.621,104.463 285.15,86.979 285.16,87.436 262.63,104.92 "/> - </g> - <g> - <path fill="#6272C3" d="M262.171,104.384l0.45,0.079l0.009,0.458l0.926,46.181l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.458l0.45,0.08L262.171,104.384z M262.655,150.479l-0.907-45.25l-44.63-7.87 - l0.907,45.25L262.655,150.479"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="233.158,78.653 234.065,123.904 189.442,116.036 188.536,70.785 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.04,134.286 166.031,133.829 188.561,116.344 188.569,116.801 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.481,134.364 166.04,134.286 188.569,116.801 189.011,116.879 "/> - </g> - <g> - <path fill="#6272C3" d="M233.581,77.809l0.45,0.08l0.009,0.457l0.926,46.182l0.009,0.457l-0.45-0.079l-45.515-8.025 - l-0.441-0.078l-0.009-0.457l-0.926-46.181l-0.009-0.458l0.441,0.078L233.581,77.809z M234.065,123.904l-0.907-45.251 - l-44.622-7.868l0.907,45.25L234.065,123.904"/> - </g> - <g> - <polygon fill="#7A88CC" points="166.031,133.829 165.105,87.647 187.635,70.163 188.561,116.344 "/> - </g> - <g> - <polygon fill="#7A88CC" points="165.105,87.647 165.096,87.19 187.625,69.706 187.635,70.163 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.006,88.27 188.536,70.786 189.442,116.036 166.913,133.521 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="166.913,133.521 166.006,88.27 188.536,70.785 189.442,116.036 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.096,87.19 187.625,69.706 188.067,69.783 165.537,87.268 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.996,142.389 166.481,134.364 189.011,116.879 234.525,124.905 "/> - </g> - <g> - <polygon fill="#769AC7" points="211.535,141.388 166.913,133.521 189.442,116.036 234.065,123.904 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.913,133.521 189.442,116.036 234.065,123.904 211.535,141.388 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.445,142.468 211.996,142.389 234.525,124.905 234.975,124.984 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.437,142.011 234.966,124.527 234.975,124.984 212.445,142.468 "/> - </g> - <g> - <polygon fill="#628CBE" points="210.628,96.138 211.535,141.388 166.913,133.521 166.006,88.27 "/> - </g> - <g> - <polygon fill="#7684CA" points="210.628,96.138 166.006,88.27 188.536,70.786 233.158,78.653 "/> - </g> - <g> - <polygon fill="#769AC7" points="166.006,88.27 188.536,70.785 233.158,78.653 210.628,96.138 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.535,141.388 210.628,96.138 233.158,78.653 234.065,123.904 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="210.628,96.138 233.158,78.653 234.065,123.904 211.535,141.388 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.537,87.268 188.067,69.783 233.581,77.809 211.052,95.293 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.511,95.83 234.04,78.345 234.966,124.527 212.437,142.011 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.052,95.293 233.581,77.809 234.031,77.888 211.501,95.373 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.501,95.373 234.031,77.888 234.04,78.345 211.511,95.83 "/> - </g> - <g> - <path fill="#6272C3" d="M211.052,95.293l0.45,0.08l0.009,0.457l0.926,46.181l0.009,0.458l-0.45-0.079l-45.514-8.025 - l-0.441-0.078l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.441,0.078L211.052,95.293z M211.535,141.388l-0.907-45.25 - l-44.622-7.868l0.907,45.251L211.535,141.388"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M182.2,68.458l0.45,0.079l0.009,0.458l0.926,46.181l0.009,0.458l-0.45-0.08l-45.514-8.025l-0.45-0.079 - l-0.009-0.458l-0.926-46.181l-0.009-0.458l0.45,0.08L182.2,68.458z M182.684,114.554l-0.907-45.25l-44.63-7.87l0.907,45.25 - L182.684,114.554"/> - </g> - <g> - <polygon fill="#628CBE" points="181.776,69.303 182.684,114.554 138.053,106.684 137.146,61.434 "/> - </g> - <g> - <polygon fill="#7A88CC" points="114.65,124.934 114.641,124.477 137.171,106.992 137.18,107.45 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.1,125.013 114.65,124.934 137.18,107.45 137.629,107.529 "/> - </g> - <g> - <polygon fill="#7A88CC" points="114.641,124.477 113.715,78.295 136.245,60.811 137.171,106.992 "/> - </g> - <g> - <polygon fill="#7A88CC" points="113.715,78.295 113.706,77.838 136.236,60.354 136.245,60.811 "/> - </g> - <g> - <polygon fill="#7A88CC" points="114.616,78.918 137.146,61.434 138.053,106.684 115.523,124.168 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="115.523,124.168 114.616,78.918 137.146,61.434 138.053,106.684 "/> - </g> - <g> - <polygon fill="#7684CA" points="113.706,77.838 136.236,60.354 136.686,60.433 114.156,77.917 "/> - </g> - <g> - <polygon fill="#7684CA" points="160.614,133.039 115.1,125.013 137.629,107.529 183.144,115.554 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.523,124.168 138.053,106.684 182.684,114.554 160.154,132.038 "/> - </g> - <g> - <polygon fill="#769AC7" points="160.154,132.038 115.523,124.168 138.053,106.684 182.684,114.554 "/> - </g> - <g> - <polygon fill="#7684CA" points="161.064,133.118 160.614,133.039 183.144,115.554 183.594,115.634 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.055,132.661 183.584,115.176 183.594,115.634 161.064,133.118 "/> - </g> - <g> - <polygon fill="#628CBE" points="159.247,86.788 160.154,132.038 115.523,124.168 114.616,78.918 "/> - </g> - <g> - <polygon fill="#7684CA" points="159.247,86.788 114.616,78.918 137.146,61.434 181.776,69.303 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.154,132.038 159.247,86.788 181.776,69.303 182.684,114.554 "/> - </g> - <g> - <polygon fill="#769AC7" points="114.616,78.918 137.146,61.434 181.776,69.303 159.247,86.788 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="159.247,86.788 181.776,69.303 182.684,114.554 160.154,132.038 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.156,77.917 136.686,60.433 182.2,68.458 159.67,85.943 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.129,86.479 182.659,68.995 183.584,115.176 161.055,132.661 "/> - </g> - <g> - <polygon fill="#7684CA" points="159.67,85.943 182.2,68.458 182.649,68.538 160.12,86.022 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.12,86.022 182.649,68.538 182.659,68.995 160.129,86.479 "/> - </g> - <g> - <path fill="#6272C3" d="M159.67,85.943l0.45,0.079l0.009,0.458l0.926,46.181l0.009,0.458l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.458l0.45,0.08L159.67,85.943z M160.154,132.038l-0.907-45.25l-44.63-7.87 - l0.907,45.25L160.154,132.038"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M130.818,8.104l0.45,0.079l0.009,0.458l0.926,46.181l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.458L84.864,0.457L84.855,0l0.45,0.079L130.818,8.104z M131.303,54.2l-0.907-45.25l-44.63-7.87 - l0.907,45.25L131.303,54.2"/> - </g> - <g> - <polygon fill="#628CBE" points="130.396,8.949 131.303,54.2 86.672,46.33 85.765,1.08 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.27,64.58 63.26,64.123 85.79,46.638 85.799,47.096 "/> - </g> - <g> - <polygon fill="#7684CA" points="63.719,64.659 63.27,64.58 85.799,47.096 86.249,47.175 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.26,64.123 62.334,17.941 84.864,0.457 85.79,46.638 "/> - </g> - <g> - <polygon fill="#7A88CC" points="62.334,17.941 62.325,17.484 84.855,0 84.864,0.457 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="64.143,63.814 63.235,18.564 85.765,1.08 86.672,46.33 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.235,18.564 85.765,1.08 86.672,46.33 64.143,63.814 "/> - </g> - <g> - <polygon fill="#7684CA" points="62.325,17.484 84.855,0 85.305,0.079 62.775,17.563 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.233,72.685 63.719,64.659 86.249,47.175 131.763,55.2 "/> - </g> - <g> - <polygon fill="#769AC7" points="108.773,71.684 64.143,63.814 86.672,46.33 131.303,54.2 "/> - </g> - <g> - <polygon fill="#7684CA" points="64.143,63.814 86.672,46.33 131.303,54.2 108.773,71.684 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.683,72.764 109.233,72.685 131.763,55.2 132.213,55.279 "/> - </g> - <g> - <polygon fill="#7A88CC" points="109.674,72.307 132.204,54.822 132.213,55.279 109.683,72.764 "/> - </g> - <g> - <polygon fill="#628CBE" points="107.866,26.434 108.773,71.684 64.143,63.814 63.235,18.564 "/> - </g> - <g> - <polygon fill="#7684CA" points="107.866,26.434 63.235,18.564 85.765,1.08 130.396,8.949 "/> - </g> - <g> - <polygon fill="#769AC7" points="63.235,18.564 85.765,1.08 130.396,8.949 107.866,26.434 "/> - </g> - <g> - <polygon fill="#7A88CC" points="108.773,71.684 107.866,26.434 130.396,8.949 131.303,54.2 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="107.866,26.434 130.396,8.949 131.303,54.2 108.773,71.684 "/> - </g> - <g> - <polygon fill="#7684CA" points="62.775,17.563 85.305,0.079 130.818,8.104 108.289,25.589 "/> - </g> - <g> - <polygon fill="#7A88CC" points="108.748,26.125 131.278,8.641 132.204,54.822 109.674,72.307 "/> - </g> - <g> - <polygon fill="#7684CA" points="108.289,25.589 130.818,8.104 131.269,8.184 108.739,25.668 "/> - </g> - <g> - <polygon fill="#7A88CC" points="108.739,25.668 131.269,8.184 131.278,8.641 108.748,26.125 "/> - </g> - <g> - <path fill="#6272C3" d="M108.289,25.589l0.45,0.079l0.009,0.458l0.926,46.181l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.458l0.45,0.08L108.289,25.589z M108.773,71.684l-0.907-45.25l-44.631-7.87 - l0.907,45.25L108.773,71.684"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="283.278,36.742 284.186,81.992 239.555,74.123 238.648,28.872 "/> - </g> - <g> - <polygon fill="#7A88CC" points="216.152,92.38 216.143,91.915 238.672,74.431 238.682,74.896 "/> - </g> - <g> - <polygon fill="#7684CA" points="216.602,92.459 216.152,92.38 238.682,74.896 239.132,74.975 "/> - </g> - <g> - <path fill="#6272C3" d="M283.702,35.897l0.45,0.079l0.009,0.458l0.926,46.181l0.009,0.465l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.465l-0.926-46.181l-0.009-0.458l0.45,0.08L283.702,35.897z M284.186,81.992l-0.907-45.25l-44.63-7.87 - l0.907,45.25L284.186,81.992"/> - </g> - <g> - <polygon fill="#7A88CC" points="216.143,91.915 215.217,45.733 237.747,28.25 238.672,74.431 "/> - </g> - <g> - <polygon fill="#7A88CC" points="215.217,45.733 215.208,45.276 237.737,27.792 237.747,28.25 "/> - </g> - <g> - <polygon fill="#7A88CC" points="216.118,46.356 238.648,28.872 239.555,74.123 217.025,91.607 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="217.025,91.607 216.118,46.356 238.648,28.872 239.555,74.123 "/> - </g> - <g> - <polygon fill="#7684CA" points="215.208,45.276 237.737,27.792 238.188,27.872 215.658,45.355 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.116,100.485 216.602,92.459 239.132,74.975 284.646,83 "/> - </g> - <g> - <polygon fill="#769AC7" points="261.656,99.477 217.025,91.607 239.555,74.123 284.186,81.992 "/> - </g> - <g> - <polygon fill="#7684CA" points="217.025,91.607 239.555,74.123 284.186,81.992 261.656,99.477 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.566,100.564 262.116,100.485 284.646,83 285.096,83.08 "/> - </g> - <g> - <polygon fill="#7A88CC" points="262.557,100.099 285.086,82.615 285.096,83.08 262.566,100.564 "/> - </g> - <g> - <polygon fill="#628CBE" points="260.749,54.226 261.656,99.477 217.025,91.607 216.118,46.356 "/> - </g> - <g> - <polygon fill="#7684CA" points="260.749,54.226 216.118,46.356 238.648,28.872 283.278,36.742 "/> - </g> - <g> - <polygon fill="#769AC7" points="216.118,46.356 238.648,28.872 283.278,36.742 260.749,54.226 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="260.749,54.226 283.278,36.742 284.186,81.992 261.656,99.477 "/> - </g> - <g> - <polygon fill="#7A88CC" points="261.656,99.477 260.749,54.226 283.278,36.742 284.186,81.992 "/> - </g> - <g> - <polygon fill="#7684CA" points="215.658,45.355 238.188,27.872 283.702,35.897 261.172,53.381 "/> - </g> - <g> - <polygon fill="#7A88CC" points="261.631,53.917 284.161,36.434 285.086,82.615 262.557,100.099 "/> - </g> - <g> - <polygon fill="#7684CA" points="261.172,53.381 283.702,35.897 284.151,35.976 261.622,53.46 "/> - </g> - <g> - <polygon fill="#7A88CC" points="261.622,53.46 284.151,35.976 284.161,36.434 261.631,53.917 "/> - </g> - <g> - <path fill="#6272C3" d="M261.172,53.381l0.45,0.08l0.009,0.457l0.926,46.182l0.009,0.465l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.465l-0.926-46.182l-0.009-0.457l0.45,0.079L261.172,53.381z M261.656,99.477l-0.907-45.251 - l-44.63-7.869l0.907,45.25L261.656,99.477"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="232.158,27.651 233.065,72.91 188.443,65.042 187.536,19.784 "/> - </g> - <g> - <polygon fill="#7A88CC" points="165.032,83.291 165.023,82.825 187.552,65.341 187.562,65.806 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.482,83.37 165.032,83.291 187.562,65.806 188.012,65.886 "/> - </g> - <g> - <path fill="#6272C3" d="M232.582,26.807l0.45,0.079l0.009,0.465l0.926,46.173l0.009,0.465l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.465l-0.925-46.173l-0.009-0.465l0.45,0.08L232.582,26.807z M233.065,72.91l-0.907-45.258l-44.622-7.868 - l0.907,45.258L233.065,72.91"/> - </g> - <g> - <polygon fill="#7A88CC" points="165.023,82.825 164.097,36.652 186.627,19.167 187.552,65.341 "/> - </g> - <g> - <polygon fill="#7A88CC" points="164.097,36.652 164.088,36.187 186.618,18.702 186.627,19.167 "/> - </g> - <g> - <polygon fill="#7A88CC" points="165.006,37.268 187.536,19.784 188.443,65.042 165.914,82.526 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="165.914,82.526 165.006,37.268 187.536,19.784 188.443,65.042 "/> - </g> - <g> - <polygon fill="#7684CA" points="164.088,36.187 186.618,18.702 187.067,18.782 164.538,36.266 "/> - </g> - <g> - <polygon fill="#7684CA" points="210.996,91.395 165.482,83.37 188.012,65.886 233.526,73.911 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.914,82.526 188.443,65.042 233.065,72.91 210.536,90.395 "/> - </g> - <g> - <polygon fill="#769AC7" points="210.536,90.395 165.914,82.526 188.443,65.042 233.065,72.91 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.446,91.475 210.996,91.395 233.526,73.911 233.976,73.99 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.437,91.009 233.966,73.525 233.976,73.99 211.446,91.475 "/> - </g> - <g> - <polygon fill="#7684CA" points="209.628,45.136 165.006,37.268 187.536,19.784 232.158,27.652 "/> - </g> - <g> - <polygon fill="#769AC7" points="165.006,37.268 187.536,19.784 232.158,27.651 209.628,45.136 "/> - </g> - <g> - <polygon fill="#7A88CC" points="210.536,90.395 209.628,45.136 232.158,27.652 233.065,72.91 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="209.628,45.136 232.158,27.651 233.065,72.91 210.536,90.395 "/> - </g> - <g> - <path fill="#6272C3" d="M210.052,44.291l0.45,0.08l0.009,0.465l0.925,46.173l0.009,0.465l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.465l-0.926-46.173l-0.009-0.465l0.45,0.079L210.052,44.291z M210.536,90.395l-0.907-45.259 - l-44.622-7.868l0.907,45.258L210.536,90.395"/> - </g> - <g> - <polygon fill="#628CBE" points="209.628,45.136 210.536,90.395 165.914,82.526 165.006,37.268 "/> - </g> - <g> - <polygon fill="#7684CA" points="164.538,36.266 187.067,18.782 232.582,26.807 210.052,44.291 "/> - </g> - <g> - <polygon fill="#7A88CC" points="210.511,44.836 233.041,27.352 233.966,73.525 211.437,91.009 "/> - </g> - <g> - <polygon fill="#7684CA" points="210.052,44.291 232.582,26.807 233.031,26.886 210.502,44.371 "/> - </g> - <g> - <polygon fill="#7A88CC" points="210.502,44.371 233.031,26.886 233.041,27.352 210.511,44.836 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="180.777,18.301 181.685,63.551 137.054,55.682 136.147,10.431 "/> - </g> - <g> - <polygon fill="#7A88CC" points="113.651,73.939 113.642,73.474 136.171,55.99 136.181,56.455 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.101,74.019 113.651,73.939 136.181,56.455 136.631,56.534 "/> - </g> - <g> - <path fill="#6272C3" d="M181.201,17.456l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.465l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.465l-0.926-46.181l-0.009-0.458l0.45,0.08L181.201,17.456z M181.685,63.551l-0.907-45.25l-44.63-7.87 - l0.907,45.25L181.685,63.551"/> - </g> - <g> - <polygon fill="#7A88CC" points="113.642,73.474 112.716,27.293 135.246,9.809 136.171,55.99 "/> - </g> - <g> - <polygon fill="#7A88CC" points="112.716,27.293 112.707,26.835 135.236,9.351 135.246,9.809 "/> - </g> - <g> - <polygon fill="#7A88CC" points="113.617,27.916 136.147,10.431 137.054,55.682 114.524,73.166 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="114.524,73.166 113.617,27.916 136.147,10.431 137.054,55.682 "/> - </g> - <g> - <polygon fill="#7684CA" points="112.707,26.835 135.236,9.351 135.687,9.431 113.157,26.915 "/> - </g> - <g> - <polygon fill="#7684CA" points="159.615,82.044 114.101,74.019 136.631,56.534 182.145,64.56 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.524,73.166 137.054,55.682 181.685,63.551 159.155,81.036 "/> - </g> - <g> - <polygon fill="#769AC7" points="159.155,81.036 114.524,73.166 137.054,55.682 181.685,63.551 "/> - </g> - <g> - <polygon fill="#7684CA" points="160.065,82.124 159.615,82.044 182.145,64.56 182.595,64.639 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.056,81.658 182.585,64.174 182.595,64.639 160.065,82.124 "/> - </g> - <g> - <polygon fill="#628CBE" points="158.248,35.785 159.155,81.036 114.524,73.166 113.617,27.916 "/> - </g> - <g> - <polygon fill="#7684CA" points="158.248,35.785 113.617,27.916 136.147,10.431 180.777,18.301 "/> - </g> - <g> - <polygon fill="#769AC7" points="113.617,27.916 136.147,10.431 180.777,18.301 158.248,35.785 "/> - </g> - <g> - <polygon fill="#7A88CC" points="159.155,81.036 158.248,35.785 180.777,18.301 181.685,63.551 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="158.248,35.785 180.777,18.301 181.685,63.551 159.155,81.036 "/> - </g> - <g> - <polygon fill="#7684CA" points="113.157,26.915 135.687,9.431 181.201,17.456 158.671,34.94 "/> - </g> - <g> - <polygon fill="#7A88CC" points="159.13,35.477 181.66,17.992 182.585,64.174 160.056,81.658 "/> - </g> - <g> - <polygon fill="#7684CA" points="158.671,34.94 181.201,17.456 181.65,17.535 159.121,35.02 "/> - </g> - <g> - <polygon fill="#7A88CC" points="159.121,35.02 181.65,17.535 181.66,17.992 159.13,35.477 "/> - </g> - <g> - <path fill="#6272C3" d="M158.671,34.94l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.465l-0.45-0.08l-45.514-8.025 - l-0.45-0.079l-0.009-0.465l-0.926-46.181l-0.009-0.458l0.45,0.08L158.671,34.94z M159.155,81.036l-0.907-45.25l-44.63-7.87 - l0.907,45.25L159.155,81.036"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M101.883,188.304l0.441,0.078l0.009,0.465l0.926,46.181l0.009,0.457l-0.441-0.078l-45.523-8.026 - l-0.441-0.078l-0.009-0.457l-0.926-46.181l-0.01-0.465l0.442,0.078L101.883,188.304z M102.359,234.405l-0.907-45.25 - l-44.622-7.868l0.907,45.251L102.359,234.405"/> - </g> - <g> - <polygon fill="#628CBE" points="101.452,189.155 102.359,234.405 57.737,226.538 56.83,181.287 "/> - </g> - <g> - <polygon fill="#7A88CC" points="34.334,244.787 34.325,244.33 56.854,226.846 56.863,227.303 "/> - </g> - <g> - <polygon fill="#7684CA" points="34.775,244.865 34.334,244.787 56.863,227.303 57.305,227.381 "/> - </g> - <g> - <polygon fill="#7A88CC" points="34.325,244.33 33.399,198.149 55.929,180.665 56.854,226.846 "/> - </g> - <g> - <polygon fill="#7A88CC" points="33.399,198.149 33.39,197.684 55.919,180.199 55.929,180.665 "/> - </g> - <g> - <polygon fill="#7A88CC" points="34.3,198.771 56.83,181.287 57.737,226.538 35.207,244.021 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="35.207,244.021 34.3,198.771 56.83,181.287 57.737,226.538 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.39,197.684 55.919,180.199 56.361,180.277 33.831,197.761 "/> - </g> - <g> - <polygon fill="#7684CA" points="80.298,252.892 34.775,244.865 57.305,227.381 102.828,235.407 "/> - </g> - <g> - <polygon fill="#769AC7" points="79.829,251.89 35.207,244.021 57.737,226.538 102.359,234.405 "/> - </g> - <g> - <polygon fill="#7684CA" points="35.207,244.021 57.737,226.538 102.359,234.405 79.829,251.89 "/> - </g> - <g> - <polygon fill="#7684CA" points="80.739,252.97 80.298,252.892 102.828,235.407 103.269,235.485 "/> - </g> - <g> - <polygon fill="#7A88CC" points="80.73,252.513 103.26,235.028 103.269,235.485 80.739,252.97 "/> - </g> - <g> - <polygon fill="#628CBE" points="78.922,206.64 79.829,251.89 35.207,244.021 34.3,198.771 "/> - </g> - <g> - <polygon fill="#7684CA" points="78.922,206.64 34.3,198.771 56.83,181.287 101.452,189.155 "/> - </g> - <g> - <polygon fill="#769AC7" points="34.3,198.771 56.83,181.287 101.452,189.155 78.922,206.64 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.829,251.89 78.922,206.64 101.452,189.155 102.359,234.405 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="78.922,206.64 101.452,189.155 102.359,234.405 79.829,251.89 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.831,197.761 56.361,180.277 101.883,188.304 79.354,205.788 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.805,206.331 102.334,188.847 103.26,235.028 80.73,252.513 "/> - </g> - <g> - <polygon fill="#7684CA" points="79.354,205.788 101.883,188.304 102.325,188.382 79.795,205.866 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.795,205.866 102.325,188.382 102.334,188.847 79.805,206.331 "/> - </g> - <g> - <path fill="#6272C3" d="M79.354,205.788l0.441,0.078l0.01,0.465l0.926,46.182l0.009,0.457l-0.441-0.078l-45.522-8.026 - l-0.441-0.078l-0.009-0.457l-0.926-46.181l-0.009-0.465l0.441,0.078L79.354,205.788z M79.829,251.89l-0.907-45.25 - L34.3,198.771l0.907,45.25L79.829,251.89"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M254.766,216.104l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.465l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.465l-0.926-46.182L208.802,208l0.442,0.078L254.766,216.104z M255.242,262.198l-0.907-45.25 - l-44.623-7.868l0.907,45.25L255.242,262.198"/> - </g> - <g> - <polygon fill="#628CBE" points="254.335,216.948 255.242,262.198 210.619,254.33 209.712,209.08 "/> - </g> - <g> - <polygon fill="#7A88CC" points="187.217,272.588 187.208,272.123 209.737,254.639 209.746,255.104 "/> - </g> - <g> - <polygon fill="#7684CA" points="187.659,272.666 187.217,272.588 209.746,255.104 210.188,255.182 "/> - </g> - <g> - <polygon fill="#7A88CC" points="187.208,272.123 186.282,225.941 208.811,208.457 209.737,254.639 "/> - </g> - <g> - <polygon fill="#7A88CC" points="186.282,225.941 186.272,225.484 208.802,208 208.811,208.457 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="188.09,271.814 187.183,226.564 209.712,209.08 210.619,254.33 "/> - </g> - <g> - <polygon fill="#7A88CC" points="187.183,226.564 209.712,209.08 210.619,254.33 188.09,271.814 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.272,225.484 208.802,208 209.244,208.078 186.714,225.563 "/> - </g> - <g> - <polygon fill="#7684CA" points="233.181,280.692 187.659,272.666 210.188,255.182 255.71,263.208 "/> - </g> - <g> - <polygon fill="#769AC7" points="232.712,279.683 188.09,271.814 210.619,254.33 255.242,262.198 "/> - </g> - <g> - <polygon fill="#7684CA" points="188.09,271.814 210.619,254.33 255.242,262.198 232.712,279.683 "/> - </g> - <g> - <polygon fill="#7684CA" points="233.623,280.771 233.181,280.692 255.71,263.208 256.152,263.286 "/> - </g> - <g> - <polygon fill="#7A88CC" points="233.613,280.306 256.143,262.821 256.152,263.286 233.623,280.771 "/> - </g> - <g> - <polygon fill="#628CBE" points="231.805,234.433 232.712,279.683 188.09,271.814 187.183,226.564 "/> - </g> - <g> - <polygon fill="#7684CA" points="231.805,234.433 187.183,226.564 209.712,209.08 254.335,216.948 "/> - </g> - <g> - <polygon fill="#769AC7" points="187.183,226.564 209.712,209.08 254.335,216.948 231.805,234.433 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.712,279.683 231.805,234.433 254.335,216.948 255.242,262.198 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="231.805,234.433 254.335,216.948 255.242,262.198 232.712,279.683 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.714,225.563 209.244,208.078 254.766,216.104 232.236,233.589 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.688,234.124 255.217,216.64 256.143,262.821 233.613,280.306 "/> - </g> - <g> - <polygon fill="#7684CA" points="232.236,233.589 254.766,216.104 255.208,216.183 232.679,233.667 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.679,233.667 255.208,216.183 255.217,216.64 232.688,234.124 "/> - </g> - <g> - <path fill="#6272C3" d="M232.236,233.589l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.465l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.465l-0.926-46.182l-0.009-0.457l0.442,0.078L232.236,233.589z M232.712,279.683l-0.907-45.25 - l-44.623-7.868l0.907,45.25L232.712,279.683"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="203.215,207.858 204.122,253.116 159.5,245.248 158.592,199.99 "/> - </g> - <g> - <polygon fill="#7A88CC" points="136.097,263.498 136.087,263.033 158.617,245.549 158.626,246.014 "/> - </g> - <g> - <polygon fill="#7684CA" points="136.539,263.576 136.097,263.498 158.626,246.014 159.068,246.092 "/> - </g> - <g> - <path fill="#6272C3" d="M203.646,207.015l0.442,0.078l0.009,0.465l0.926,46.174l0.009,0.465l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.465l-0.925-46.173l-0.009-0.465l0.442,0.078L203.646,207.015z M204.122,253.116l-0.907-45.258 - l-44.623-7.869l0.907,45.258L204.122,253.116"/> - </g> - <g> - <polygon fill="#7A88CC" points="136.087,263.033 135.162,216.859 157.691,199.375 158.617,245.549 "/> - </g> - <g> - <polygon fill="#7A88CC" points="135.162,216.859 135.152,216.395 157.682,198.91 157.691,199.375 "/> - </g> - <g> - <polygon fill="#7A88CC" points="136.063,217.475 158.592,199.99 159.5,245.248 136.97,262.732 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="136.97,262.732 136.063,217.475 158.592,199.99 159.5,245.248 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.152,216.395 157.682,198.91 158.124,198.988 135.594,216.473 "/> - </g> - <g> - <polygon fill="#7684CA" points="182.061,271.603 136.539,263.576 159.068,246.092 204.59,254.118 "/> - </g> - <g> - <polygon fill="#769AC7" points="181.592,270.601 136.97,262.732 159.5,245.248 204.122,253.116 "/> - </g> - <g> - <polygon fill="#7684CA" points="136.97,262.732 159.5,245.248 204.122,253.116 181.592,270.601 "/> - </g> - <g> - <polygon fill="#7684CA" points="182.502,271.681 182.061,271.603 204.59,254.118 205.032,254.196 "/> - </g> - <g> - <polygon fill="#7A88CC" points="182.493,271.216 205.023,253.731 205.032,254.196 182.502,271.681 "/> - </g> - <g> - <polygon fill="#7684CA" points="180.685,225.343 136.063,217.475 158.592,199.99 203.215,207.858 "/> - </g> - <g> - <polygon fill="#769AC7" points="136.063,217.475 158.592,199.99 203.215,207.858 180.685,225.343 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.592,270.601 180.685,225.343 203.215,207.858 204.122,253.116 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="180.685,225.343 203.215,207.858 204.122,253.116 181.592,270.601 "/> - </g> - <g> - <path fill="#6272C3" d="M181.116,224.499l0.442,0.078l0.009,0.465l0.925,46.174l0.009,0.465l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.465l-0.926-46.174l-0.009-0.465l0.442,0.078L181.116,224.499z M181.592,270.601l-0.907-45.258 - l-44.623-7.868l0.907,45.258L181.592,270.601"/> - </g> - <g> - <polygon fill="#628CBE" points="180.685,225.343 181.592,270.601 136.97,262.732 136.063,217.475 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.594,216.473 158.124,198.988 203.646,207.015 181.116,224.499 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.568,225.042 204.097,207.558 205.023,253.731 182.493,271.216 "/> - </g> - <g> - <polygon fill="#7684CA" points="181.116,224.499 203.646,207.015 204.088,207.093 181.559,224.577 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.559,224.577 204.088,207.093 204.097,207.558 181.568,225.042 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="151.834,198.506 152.741,243.757 108.118,235.889 107.211,190.638 "/> - </g> - <g> - <polygon fill="#7A88CC" points="84.716,254.147 84.707,253.682 107.236,236.197 107.245,236.663 "/> - </g> - <g> - <polygon fill="#7684CA" points="85.158,254.226 84.716,254.147 107.245,236.663 107.688,236.741 "/> - </g> - <g> - <path fill="#6272C3" d="M152.265,197.663l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.466l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.466l-0.926-46.182l-0.009-0.457l0.442,0.078L152.265,197.663z M152.741,243.757l-0.907-45.25 - l-44.623-7.868l0.907,45.25L152.741,243.757"/> - </g> - <g> - <polygon fill="#7A88CC" points="84.707,253.682 83.781,207.5 106.31,190.016 107.236,236.197 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.781,207.5 83.771,207.043 106.301,189.559 106.31,190.016 "/> - </g> - <g> - <polygon fill="#7A88CC" points="84.682,208.123 107.211,190.638 108.118,235.889 85.589,253.373 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="85.589,253.373 84.682,208.123 107.211,190.638 108.118,235.889 "/> - </g> - <g> - <polygon fill="#7684CA" points="83.771,207.043 106.301,189.559 106.743,189.636 84.213,207.121 "/> - </g> - <g> - <polygon fill="#7684CA" points="130.68,262.252 85.158,254.226 107.688,236.741 153.209,244.768 "/> - </g> - <g> - <polygon fill="#769AC7" points="130.211,261.241 85.589,253.373 108.118,235.889 152.741,243.757 "/> - </g> - <g> - <polygon fill="#7684CA" points="85.589,253.373 108.118,235.889 152.741,243.757 130.211,261.241 "/> - </g> - <g> - <polygon fill="#7684CA" points="131.122,262.33 130.68,262.252 153.209,244.768 153.651,244.846 "/> - </g> - <g> - <polygon fill="#7A88CC" points="131.112,261.864 153.642,244.38 153.651,244.846 131.122,262.33 "/> - </g> - <g> - <polygon fill="#628CBE" points="129.304,215.991 130.211,261.241 85.589,253.373 84.682,208.123 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.304,215.991 84.682,208.123 107.211,190.638 151.834,198.506 "/> - </g> - <g> - <polygon fill="#769AC7" points="84.682,208.123 107.211,190.638 151.834,198.506 129.304,215.991 "/> - </g> - <g> - <polygon fill="#7A88CC" points="130.211,261.241 129.304,215.991 151.834,198.506 152.741,243.757 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="129.304,215.991 151.834,198.506 152.741,243.757 130.211,261.241 "/> - </g> - <g> - <polygon fill="#7684CA" points="84.213,207.121 106.743,189.636 152.265,197.663 129.735,215.147 "/> - </g> - <g> - <polygon fill="#7A88CC" points="130.187,215.683 152.716,198.198 153.642,244.38 131.112,261.864 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.735,215.147 152.265,197.663 152.707,197.741 130.178,215.226 "/> - </g> - <g> - <polygon fill="#7A88CC" points="130.178,215.226 152.707,197.741 152.716,198.198 130.187,215.683 "/> - </g> - <g> - <path fill="#6272C3" d="M129.735,215.147l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.466l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.466L83.781,207.5l-0.009-0.457l0.442,0.078L129.735,215.147z M130.211,261.241l-0.907-45.25 - l-44.623-7.868l0.907,45.25L130.211,261.241"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="100.453,138.153 101.359,183.403 56.737,175.535 55.831,130.285 "/> - </g> - <g> - <polygon fill="#7A88CC" points="33.335,193.793 33.326,193.328 55.855,175.844 55.864,176.309 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.777,193.871 33.335,193.793 55.864,176.309 56.307,176.387 "/> - </g> - <g> - <path fill="#6272C3" d="M100.884,137.31l0.441,0.078l0.009,0.457l0.926,46.181l0.009,0.465l-0.441-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.465l-0.926-46.182l-0.009-0.457l0.442,0.078L100.884,137.31z M101.359,183.403l-0.907-45.25 - l-44.623-7.868l0.907,45.25L101.359,183.403"/> - </g> - <g> - <polygon fill="#7A88CC" points="33.326,193.328 32.4,147.146 54.929,129.662 55.855,175.844 "/> - </g> - <g> - <polygon fill="#7A88CC" points="32.4,147.146 32.391,146.689 54.92,129.205 54.929,129.662 "/> - </g> - <g> - <polygon fill="#7A88CC" points="33.301,147.769 55.83,130.285 56.737,175.535 34.208,193.02 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="34.208,193.02 33.301,147.769 55.831,130.285 56.737,175.535 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.391,146.689 54.92,129.205 55.362,129.283 32.833,146.767 "/> - </g> - <g> - <polygon fill="#7684CA" points="79.299,201.897 33.777,193.871 56.307,176.387 101.829,184.414 "/> - </g> - <g> - <polygon fill="#769AC7" points="78.83,200.888 34.208,193.02 56.737,175.535 101.359,183.403 "/> - </g> - <g> - <polygon fill="#7684CA" points="34.208,193.02 56.737,175.535 101.359,183.403 78.83,200.888 "/> - </g> - <g> - <polygon fill="#7684CA" points="79.74,201.976 79.299,201.897 101.829,184.414 102.27,184.491 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.731,201.511 102.261,184.026 102.27,184.491 79.74,201.976 "/> - </g> - <g> - <polygon fill="#628CBE" points="77.923,155.637 78.83,200.888 34.208,193.02 33.301,147.769 "/> - </g> - <g> - <polygon fill="#7684CA" points="77.923,155.637 33.301,147.769 55.83,130.285 100.453,138.153 "/> - </g> - <g> - <polygon fill="#769AC7" points="33.301,147.769 55.831,130.285 100.453,138.153 77.923,155.637 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.83,200.888 77.923,155.637 100.453,138.153 101.359,183.403 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="77.923,155.637 100.453,138.153 101.359,183.403 78.83,200.888 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.833,146.767 55.362,129.283 100.884,137.31 78.354,154.794 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.805,155.329 101.335,137.845 102.261,184.026 79.731,201.511 "/> - </g> - <g> - <polygon fill="#7684CA" points="78.354,154.794 100.884,137.31 101.326,137.388 78.796,154.872 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.796,154.872 101.326,137.388 101.335,137.845 78.805,155.329 "/> - </g> - <g> - <path fill="#6272C3" d="M78.354,154.794l0.441,0.078l0.009,0.457l0.926,46.182l0.009,0.465l-0.441-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.465L32.4,147.146l-0.009-0.457l0.442,0.078L78.354,154.794z M78.83,200.888l-0.907-45.25 - l-44.622-7.868l0.907,45.25L78.83,200.888"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="253.336,165.945 254.243,211.203 209.621,203.335 208.713,158.077 "/> - </g> - <g> - <polygon fill="#7A88CC" points="186.218,221.585 186.208,221.12 208.738,203.636 208.747,204.101 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.66,221.663 186.218,221.585 208.747,204.101 209.189,204.179 "/> - </g> - <g> - <path fill="#6272C3" d="M253.767,165.102l0.442,0.078l0.009,0.465l0.926,46.173l0.009,0.465l-0.442-0.077l-45.522-8.027 - l-0.442-0.078l-0.009-0.465l-0.925-46.173l-0.009-0.465l0.442,0.078L253.767,165.102z M254.243,211.203l-0.907-45.258 - l-44.623-7.868l0.907,45.258L254.243,211.203"/> - </g> - <g> - <polygon fill="#7A88CC" points="186.208,221.12 185.283,174.947 207.813,157.462 208.738,203.636 "/> - </g> - <g> - <polygon fill="#7A88CC" points="185.283,174.947 185.273,174.481 207.803,156.997 207.813,157.462 "/> - </g> - <g> - <polygon fill="#7A88CC" points="186.184,175.561 208.713,158.077 209.621,203.335 187.091,220.819 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="187.091,220.819 186.184,175.561 208.713,158.077 209.621,203.335 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.273,174.481 207.803,156.997 208.245,157.075 185.715,174.559 "/> - </g> - <g> - <polygon fill="#7684CA" points="232.182,229.689 186.66,221.663 209.189,204.179 254.711,212.206 "/> - </g> - <g> - <polygon fill="#7684CA" points="187.091,220.819 209.621,203.335 254.243,211.203 231.713,228.688 "/> - </g> - <g> - <polygon fill="#769AC7" points="231.713,228.688 187.091,220.819 209.621,203.335 254.243,211.203 "/> - </g> - <g> - <polygon fill="#7684CA" points="232.624,229.768 232.182,229.689 254.711,212.206 255.153,212.283 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.614,229.303 255.144,211.818 255.153,212.283 232.624,229.768 "/> - </g> - <g> - <polygon fill="#7684CA" points="230.806,183.429 186.184,175.561 208.713,158.077 253.336,165.945 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.713,228.688 230.806,183.429 253.336,165.945 254.243,211.203 "/> - </g> - <g> - <polygon fill="#769AC7" points="186.184,175.561 208.713,158.077 253.336,165.945 230.806,183.429 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="230.806,183.429 253.336,165.945 254.243,211.203 231.713,228.688 "/> - </g> - <g> - <path fill="#6272C3" d="M231.237,182.586l0.442,0.078l0.009,0.465l0.925,46.173l0.009,0.465l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.465l-0.926-46.173l-0.009-0.465l0.442,0.078L231.237,182.586z M231.713,228.688l-0.907-45.258 - l-44.623-7.868l0.907,45.258L231.713,228.688"/> - </g> - <g> - <polygon fill="#628CBE" points="230.806,183.429 231.713,228.688 187.091,220.819 186.184,175.561 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.715,174.559 208.245,157.075 253.767,165.102 231.237,182.586 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.689,183.129 254.218,165.645 255.144,211.818 232.614,229.303 "/> - </g> - <g> - <polygon fill="#7684CA" points="231.237,182.586 253.767,165.102 254.209,165.18 231.68,182.664 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.68,182.664 254.209,165.18 254.218,165.645 231.689,183.129 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="202.216,156.855 203.123,202.113 158.5,194.246 157.593,148.987 "/> - </g> - <g> - <polygon fill="#7A88CC" points="135.098,212.495 135.088,212.038 157.618,194.554 157.627,195.011 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.54,212.573 135.098,212.495 157.627,195.011 158.069,195.089 "/> - </g> - <g> - <path fill="#6272C3" d="M202.647,156.012l0.442,0.078l0.009,0.465l0.926,46.181l0.009,0.457l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.457l-0.926-46.181l-0.009-0.465l0.442,0.078L202.647,156.012z M203.123,202.113l-0.907-45.258 - l-44.623-7.868l0.907,45.259L203.123,202.113"/> - </g> - <g> - <polygon fill="#7A88CC" points="135.088,212.038 134.163,165.857 156.692,148.373 157.618,194.554 "/> - </g> - <g> - <polygon fill="#7A88CC" points="134.163,165.857 134.153,165.392 156.683,147.907 156.692,148.373 "/> - </g> - <g> - <polygon fill="#7A88CC" points="135.063,166.471 157.593,148.987 158.5,194.246 135.971,211.729 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="135.971,211.729 135.063,166.471 157.593,148.987 158.5,194.246 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.153,165.392 156.683,147.907 157.125,147.985 134.595,165.469 "/> - </g> - <g> - <polygon fill="#7684CA" points="181.062,220.6 135.54,212.573 158.069,195.089 203.591,203.115 "/> - </g> - <g> - <polygon fill="#769AC7" points="180.593,219.598 135.971,211.729 158.5,194.246 203.123,202.113 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.971,211.729 158.5,194.246 203.123,202.113 180.593,219.598 "/> - </g> - <g> - <polygon fill="#7684CA" points="181.503,220.678 181.062,220.6 203.591,203.115 204.033,203.193 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.495,220.221 204.024,202.736 204.033,203.193 181.503,220.678 "/> - </g> - <g> - <polygon fill="#7684CA" points="179.686,174.339 135.063,166.471 157.593,148.987 202.216,156.855 "/> - </g> - <g> - <polygon fill="#769AC7" points="135.063,166.471 157.593,148.987 202.216,156.855 179.686,174.339 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="179.686,174.339 202.216,156.855 203.123,202.113 180.593,219.598 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.593,219.598 179.686,174.339 202.216,156.855 203.123,202.113 "/> - </g> - <g> - <path fill="#6272C3" d="M180.117,173.496l0.442,0.078l0.009,0.465l0.926,46.181l0.009,0.457l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.457l-0.926-46.181l-0.009-0.465l0.442,0.078L180.117,173.496z M180.593,219.598l-0.907-45.258 - l-44.623-7.868l0.907,45.258L180.593,219.598"/> - </g> - <g> - <polygon fill="#628CBE" points="135.063,166.471 135.971,211.729 180.593,219.598 179.686,174.339 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.595,165.469 157.125,147.985 202.647,156.012 180.117,173.496 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.569,174.04 203.098,156.555 204.024,202.736 181.495,220.221 "/> - </g> - <g> - <polygon fill="#7684CA" points="180.117,173.496 202.647,156.012 203.089,156.09 180.56,173.574 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.56,173.574 203.089,156.09 203.098,156.555 180.569,174.04 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="150.835,147.504 151.742,192.763 107.12,184.895 106.212,139.636 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.717,203.144 83.708,202.679 106.237,185.194 106.246,185.66 "/> - </g> - <g> - <polygon fill="#7684CA" points="84.159,203.222 83.717,203.144 106.246,185.66 106.688,185.737 "/> - </g> - <g> - <path fill="#6272C3" d="M151.266,146.661l0.442,0.078l0.009,0.465l0.926,46.173l0.009,0.465l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.465l-0.925-46.173l-0.009-0.465l0.442,0.078L151.266,146.661z M151.742,192.763l-0.907-45.259 - l-44.623-7.868l0.907,45.259L151.742,192.763"/> - </g> - <g> - <polygon fill="#7A88CC" points="83.708,202.679 82.782,156.506 105.312,139.021 106.237,185.194 "/> - </g> - <g> - <polygon fill="#7A88CC" points="82.782,156.506 82.772,156.04 105.302,138.556 105.312,139.021 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.683,157.12 106.212,139.636 107.12,184.895 84.59,202.379 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="84.59,202.379 83.683,157.12 106.212,139.636 107.12,184.895 "/> - </g> - <g> - <polygon fill="#7684CA" points="82.772,156.04 105.302,138.556 105.744,138.634 83.214,156.118 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.681,211.248 84.159,203.222 106.688,185.737 152.21,193.764 "/> - </g> - <g> - <polygon fill="#7684CA" points="84.59,202.379 107.12,184.895 151.742,192.763 129.212,210.247 "/> - </g> - <g> - <polygon fill="#769AC7" points="129.212,210.247 84.59,202.379 107.12,184.895 151.742,192.763 "/> - </g> - <g> - <polygon fill="#7684CA" points="130.123,211.326 129.681,211.248 152.21,193.764 152.652,193.842 "/> - </g> - <g> - <polygon fill="#7A88CC" points="130.113,210.861 152.643,193.377 152.652,193.842 130.123,211.326 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.305,164.988 83.683,157.12 106.212,139.636 150.835,147.504 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="128.305,164.988 150.835,147.504 151.742,192.763 129.212,210.247 "/> - </g> - <g> - <polygon fill="#7A88CC" points="129.212,210.247 128.305,164.988 150.835,147.504 151.742,192.763 "/> - </g> - <g> - <polygon fill="#769AC7" points="83.683,157.12 106.212,139.636 150.835,147.504 128.305,164.988 "/> - </g> - <g> - <path fill="#6272C3" d="M128.736,164.145l0.442,0.078l0.009,0.466l0.925,46.173l0.009,0.465l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.465l-0.926-46.173l-0.009-0.466l0.442,0.078L128.736,164.145z M129.212,210.247l-0.907-45.259 - l-44.623-7.868l0.907,45.259L129.212,210.247"/> - </g> - <g> - <polygon fill="#628CBE" points="128.305,164.988 129.212,210.247 84.59,202.379 83.683,157.12 "/> - </g> - <g> - <polygon fill="#7684CA" points="83.214,156.118 105.744,138.634 151.266,146.661 128.736,164.145 "/> - </g> - <g> - <polygon fill="#7A88CC" points="129.188,164.688 151.717,147.204 152.643,193.377 130.113,210.861 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.736,164.145 151.266,146.661 151.708,146.739 129.179,164.223 "/> - </g> - <g> - <polygon fill="#7A88CC" points="129.179,164.223 151.708,146.739 151.717,147.204 129.188,164.688 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="100.039,86.979 100.946,132.229 56.323,124.361 55.417,79.11 "/> - </g> - <g> - <polygon fill="#7A88CC" points="32.921,142.61 32.912,142.154 55.441,124.669 55.45,125.126 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.363,142.688 32.921,142.61 55.45,125.126 55.892,125.204 "/> - </g> - <g> - <path fill="#6272C3" d="M100.47,86.135l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.457l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.442,0.078L100.47,86.135z M100.946,132.229l-0.907-45.25 - L55.417,79.11l0.907,45.25L100.946,132.229"/> - </g> - <g> - <polygon fill="#7A88CC" points="32.912,142.154 31.986,95.972 54.516,78.487 55.441,124.669 "/> - </g> - <g> - <polygon fill="#7A88CC" points="31.986,95.972 31.977,95.515 54.506,78.031 54.516,78.487 "/> - </g> - <g> - <polygon fill="#7A88CC" points="32.887,96.595 55.417,79.11 56.323,124.361 33.794,141.845 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="33.794,141.845 32.887,96.595 55.417,79.11 56.323,124.361 "/> - </g> - <g> - <polygon fill="#7684CA" points="31.977,95.515 54.506,78.031 54.948,78.108 32.418,95.593 "/> - </g> - <g> - <polygon fill="#7684CA" points="78.885,150.715 33.363,142.688 55.892,125.204 101.415,133.231 "/> - </g> - <g> - <polygon fill="#769AC7" points="78.417,149.713 33.794,141.845 56.323,124.361 100.946,132.229 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.794,141.845 56.323,124.361 100.946,132.229 78.417,149.713 "/> - </g> - <g> - <polygon fill="#7684CA" points="79.327,150.793 78.885,150.715 101.415,133.231 101.856,133.309 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.317,150.336 101.847,132.852 101.856,133.309 79.327,150.793 "/> - </g> - <g> - <polygon fill="#628CBE" points="77.509,104.463 78.417,149.713 33.794,141.845 32.887,96.595 "/> - </g> - <g> - <polygon fill="#7684CA" points="77.509,104.463 32.887,96.595 55.417,79.11 100.039,86.979 "/> - </g> - <g> - <polygon fill="#769AC7" points="32.887,96.595 55.417,79.11 100.039,86.979 77.509,104.463 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.417,149.713 77.509,104.463 100.039,86.979 100.946,132.229 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="77.509,104.463 100.039,86.979 100.946,132.229 78.417,149.713 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.418,95.593 54.948,78.108 100.47,86.135 77.941,103.62 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.392,104.154 100.921,86.67 101.847,132.852 79.317,150.336 "/> - </g> - <g> - <polygon fill="#7684CA" points="77.941,103.62 100.47,86.135 100.912,86.213 78.383,103.698 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.383,103.698 100.912,86.213 100.921,86.67 78.392,104.154 "/> - </g> - <g> - <path fill="#6272C3" d="M77.941,103.62l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.457l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.442,0.078L77.941,103.62z M78.417,149.713l-0.907-45.25 - l-44.623-7.868l0.907,45.25L78.417,149.713"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M253.353,113.928l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.465l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.465l-0.926-46.182l-0.009-0.457l0.442,0.078L253.353,113.928z M253.829,160.021l-0.907-45.25 - l-44.623-7.868l0.907,45.25L253.829,160.021"/> - </g> - <g> - <polygon fill="#628CBE" points="252.922,114.771 253.829,160.021 209.206,152.153 208.299,106.903 "/> - </g> - <g> - <polygon fill="#7A88CC" points="185.804,170.411 185.794,169.946 208.324,152.461 208.333,152.927 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.246,170.489 185.804,170.411 208.333,152.927 208.775,153.005 "/> - </g> - <g> - <polygon fill="#7A88CC" points="185.794,169.946 184.869,123.764 207.398,106.28 208.324,152.461 "/> - </g> - <g> - <polygon fill="#7A88CC" points="184.869,123.764 184.859,123.308 207.389,105.823 207.398,106.28 "/> - </g> - <g> - <polygon fill="#7A88CC" points="185.77,124.387 208.299,106.903 209.206,152.153 186.677,169.637 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="186.677,169.637 185.77,124.387 208.299,106.903 209.206,152.153 "/> - </g> - <g> - <polygon fill="#7684CA" points="184.859,123.308 207.389,105.823 207.831,105.901 185.301,123.385 "/> - </g> - <g> - <polygon fill="#7684CA" points="231.768,178.516 186.246,170.489 208.775,153.005 254.297,161.032 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.677,169.637 209.206,152.153 253.829,160.021 231.299,177.505 "/> - </g> - <g> - <polygon fill="#769AC7" points="231.299,177.505 186.677,169.637 209.206,152.153 253.829,160.021 "/> - </g> - <g> - <polygon fill="#7684CA" points="232.209,178.594 231.768,178.516 254.297,161.032 254.739,161.109 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.2,178.128 254.73,160.644 254.739,161.109 232.209,178.594 "/> - </g> - <g> - <polygon fill="#628CBE" points="230.392,132.255 231.299,177.505 186.677,169.637 185.77,124.387 "/> - </g> - <g> - <polygon fill="#7684CA" points="230.392,132.255 185.77,124.387 208.299,106.903 252.922,114.771 "/> - </g> - <g> - <polygon fill="#769AC7" points="185.77,124.387 208.299,106.903 252.922,114.771 230.392,132.255 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.299,177.505 230.392,132.255 252.922,114.771 253.829,160.021 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="230.392,132.255 252.922,114.771 253.829,160.021 231.299,177.505 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.301,123.385 207.831,105.901 253.353,113.928 230.823,131.412 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.274,131.947 253.804,114.462 254.73,160.644 232.2,178.128 "/> - </g> - <g> - <polygon fill="#7684CA" points="230.823,131.412 253.353,113.928 253.795,114.006 231.265,131.49 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.265,131.49 253.795,114.006 253.804,114.462 231.274,131.947 "/> - </g> - <g> - <path fill="#6272C3" d="M230.823,131.412l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.465l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.465l-0.926-46.182l-0.009-0.457l0.442,0.078L230.823,131.412z M231.299,177.505l-0.907-45.25 - l-44.623-7.868l0.907,45.25L231.299,177.505"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="201.802,105.681 202.709,150.939 158.086,143.071 157.179,97.813 "/> - </g> - <g> - <polygon fill="#7A88CC" points="134.684,161.321 134.674,160.855 157.204,143.371 157.213,143.837 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.134,161.4 134.684,161.321 157.213,143.837 157.663,143.916 "/> - </g> - <g> - <polygon fill="#7A88CC" points="134.674,160.855 133.749,114.683 156.278,97.198 157.204,143.371 "/> - </g> - <g> - <polygon fill="#7A88CC" points="134.649,115.297 157.179,97.813 158.086,143.071 135.557,160.556 "/> - </g> - <g> - <polygon fill="#7684CA" points="180.647,169.426 135.134,161.4 157.663,143.916 203.177,151.941 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="135.557,160.556 134.649,115.297 157.179,97.813 158.086,143.071 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.557,160.556 158.086,143.071 202.709,150.939 180.179,168.424 "/> - </g> - <g> - <polygon fill="#769AC7" points="180.179,168.424 135.557,160.556 158.086,143.071 202.709,150.939 "/> - </g> - <g> - <polygon fill="#6272C3" points="203.61,151.554 202.684,105.381 202.675,104.916 202.233,104.837 156.719,96.812 - 156.269,96.733 156.278,97.198 156.304,98.492 157.179,97.813 201.802,105.681 202.709,150.939 201.744,151.688 - 203.177,151.941 203.619,152.02 "/> - </g> - <g> - <polygon fill="#7A88CC" points="133.749,114.683 133.739,114.217 156.269,96.733 156.278,97.198 "/> - </g> - <g> - <polygon fill="#7684CA" points="133.739,114.217 156.269,96.733 156.719,96.812 134.189,114.296 "/> - </g> - <g> - <polygon fill="#7684CA" points="181.089,169.503 180.647,169.426 203.177,151.941 203.619,152.02 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.08,169.038 203.61,151.554 203.619,152.02 181.089,169.503 "/> - </g> - <g> - <polygon fill="#628CBE" points="179.272,123.165 180.179,168.424 135.557,160.556 134.649,115.297 "/> - </g> - <g> - <polygon fill="#7684CA" points="179.272,123.165 134.649,115.297 157.179,97.813 201.802,105.681 "/> - </g> - <g> - <polygon fill="#769AC7" points="134.649,115.297 157.179,97.813 201.802,105.681 179.272,123.165 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.179,168.424 179.272,123.165 201.802,105.681 202.709,150.939 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="179.272,123.165 201.802,105.681 202.709,150.939 180.179,168.424 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.189,114.296 156.719,96.812 202.233,104.837 179.703,122.322 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.155,122.865 202.684,105.381 203.61,151.554 181.08,169.038 "/> - </g> - <g> - <polygon fill="#7684CA" points="179.703,122.322 202.233,104.837 202.675,104.916 180.146,122.399 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.146,122.399 202.675,104.916 202.684,105.381 180.155,122.865 "/> - </g> - <g> - <path fill="#6272C3" d="M179.703,122.322l0.442,0.078l0.009,0.466l0.925,46.173l0.009,0.465l-0.442-0.078l-45.514-8.025 - l-0.45-0.08l-0.009-0.465l-0.926-46.173l-0.009-0.466l0.45,0.08L179.703,122.322z M180.179,168.424l-0.907-45.259 - l-44.623-7.868l0.907,45.259L180.179,168.424"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="150.421,96.329 151.328,141.58 106.706,133.712 105.798,88.461 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.303,151.961 83.293,151.504 105.823,134.02 105.832,134.477 "/> - </g> - <g> - <polygon fill="#7684CA" points="83.745,152.04 83.303,151.961 105.832,134.477 106.274,134.555 "/> - </g> - <g> - <path fill="#6272C3" d="M150.852,95.486l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.457l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.442,0.078L150.852,95.486z M151.328,141.58l-0.907-45.251 - l-44.623-7.868l0.907,45.251L151.328,141.58"/> - </g> - <g> - <polygon fill="#7A88CC" points="83.293,151.504 82.368,105.323 104.897,87.838 105.823,134.02 "/> - </g> - <g> - <polygon fill="#7A88CC" points="82.368,105.323 82.358,104.866 104.888,87.381 104.897,87.838 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.269,105.945 105.798,88.461 106.705,133.712 84.176,151.196 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="84.176,151.196 83.269,105.945 105.798,88.461 106.706,133.712 "/> - </g> - <g> - <polygon fill="#7684CA" points="82.358,104.866 104.888,87.381 105.33,87.459 82.8,104.943 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.267,160.066 83.745,152.04 106.274,134.555 151.796,142.582 "/> - </g> - <g> - <polygon fill="#7684CA" points="84.176,151.196 106.705,133.712 151.328,141.58 128.798,159.064 "/> - </g> - <g> - <polygon fill="#769AC7" points="128.798,159.064 84.176,151.196 106.706,133.712 151.328,141.58 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.708,160.144 129.267,160.066 151.796,142.582 152.238,142.66 "/> - </g> - <g> - <polygon fill="#7A88CC" points="129.699,159.687 152.229,142.203 152.238,142.66 129.708,160.144 "/> - </g> - <g> - <polygon fill="#628CBE" points="127.891,113.813 128.798,159.064 84.176,151.196 83.269,105.945 "/> - </g> - <g> - <polygon fill="#7684CA" points="127.891,113.813 83.269,105.945 105.798,88.461 150.421,96.329 "/> - </g> - <g> - <polygon fill="#769AC7" points="83.269,105.945 105.798,88.461 150.421,96.329 127.891,113.813 "/> - </g> - <g> - <polygon fill="#7A88CC" points="128.798,159.064 127.891,113.813 150.421,96.329 151.328,141.58 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="127.891,113.813 150.421,96.329 151.328,141.58 128.798,159.064 "/> - </g> - <g> - <polygon fill="#7684CA" points="82.8,104.943 105.33,87.459 150.852,95.486 128.323,112.97 "/> - </g> - <g> - <polygon fill="#7A88CC" points="128.773,113.505 151.303,96.021 152.229,142.203 129.699,159.687 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.323,112.97 150.852,95.486 151.294,95.564 128.765,113.048 "/> - </g> - <g> - <polygon fill="#7A88CC" points="128.765,113.048 151.294,95.564 151.303,96.021 128.773,113.505 "/> - </g> - <g> - <path fill="#6272C3" d="M128.323,112.97l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.457l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.442,0.078L128.323,112.97z M128.798,159.064l-0.907-45.251 - l-44.623-7.868l0.907,45.251L128.798,159.064"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="99.04,35.976 99.947,81.235 55.325,73.367 54.417,28.107 "/> - </g> - <g> - <polygon fill="#7A88CC" points="31.922,91.616 31.913,91.15 54.442,73.666 54.451,74.132 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.364,91.694 31.922,91.616 54.451,74.132 54.894,74.21 "/> - </g> - <g> - <path fill="#6272C3" d="M99.471,35.132l0.442,0.078l0.009,0.457l0.926,46.181l0.009,0.466l-0.442-0.078L54.894,74.21 - l-0.442-0.078l-0.009-0.466l-0.926-46.181l-0.009-0.457l0.442,0.078L99.471,35.132z M99.947,81.235L99.04,35.976 - l-44.623-7.868l0.907,45.259L99.947,81.235"/> - </g> - <g> - <polygon fill="#7A88CC" points="31.913,91.15 30.987,44.969 53.516,27.485 54.442,73.666 "/> - </g> - <g> - <polygon fill="#7A88CC" points="30.987,44.969 30.978,44.512 53.507,27.028 53.516,27.485 "/> - </g> - <g> - <polygon fill="#7A88CC" points="31.888,45.592 54.417,28.107 55.325,73.367 32.795,90.851 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="32.795,90.851 31.888,45.592 54.417,28.107 55.325,73.367 "/> - </g> - <g> - <polygon fill="#7684CA" points="30.978,44.512 53.507,27.028 53.949,27.105 31.419,44.59 "/> - </g> - <g> - <polygon fill="#7684CA" points="77.886,99.721 32.364,91.694 54.894,74.21 100.416,82.237 "/> - </g> - <g> - <polygon fill="#769AC7" points="77.417,98.719 32.795,90.851 55.325,73.367 99.947,81.235 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.795,90.851 55.325,73.367 99.947,81.235 77.417,98.719 "/> - </g> - <g> - <polygon fill="#7684CA" points="78.328,99.799 77.886,99.721 100.416,82.237 100.857,82.314 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.318,99.333 100.848,81.849 100.857,82.314 78.328,99.799 "/> - </g> - <g> - <polygon fill="#628CBE" points="76.51,53.46 77.417,98.719 32.795,90.851 31.888,45.592 "/> - </g> - <g> - <polygon fill="#7684CA" points="76.51,53.46 31.888,45.592 54.417,28.107 99.04,35.976 "/> - </g> - <g> - <polygon fill="#769AC7" points="31.888,45.592 54.417,28.107 99.04,35.976 76.51,53.46 "/> - </g> - <g> - <polygon fill="#7A88CC" points="77.417,98.719 76.51,53.46 99.04,35.976 99.947,81.235 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="76.51,53.46 99.04,35.976 99.947,81.235 77.417,98.719 "/> - </g> - <g> - <polygon fill="#7684CA" points="31.419,44.59 53.949,27.105 99.471,35.132 76.941,52.617 "/> - </g> - <g> - <polygon fill="#7A88CC" points="77.393,53.152 99.922,35.667 100.848,81.849 78.318,99.333 "/> - </g> - <g> - <polygon fill="#7684CA" points="76.941,52.617 99.471,35.132 99.913,35.21 77.384,52.695 "/> - </g> - <g> - <polygon fill="#7A88CC" points="77.384,52.695 99.913,35.21 99.922,35.667 77.393,53.152 "/> - </g> - <g> - <path fill="#6272C3" d="M76.941,52.617l0.442,0.078l0.009,0.457l0.926,46.181l0.009,0.466l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.466l-0.926-46.181l-0.009-0.457l0.442,0.078L76.941,52.617z M77.417,98.719L76.51,53.46l-44.623-7.868 - l0.907,45.259L77.417,98.719"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="251.922,63.769 252.83,109.027 208.207,101.159 207.3,55.9 "/> - </g> - <g> - <polygon fill="#7A88CC" points="184.796,119.408 184.787,118.942 207.316,101.458 207.326,101.923 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.246,119.487 184.796,119.408 207.326,101.923 207.775,102.002 "/> - </g> - <g> - <path fill="#6272C3" d="M252.354,62.925l0.442,0.078l0.009,0.466l0.926,46.173l0.009,0.466l-0.442-0.078l-45.522-8.027 - l-0.45-0.079l-0.009-0.466l-0.925-46.173l-0.009-0.466l0.45,0.08L252.354,62.925z M252.83,109.027l-0.907-45.259L207.3,55.9 - l0.907,45.259L252.83,109.027"/> - </g> - <g> - <polygon fill="#7A88CC" points="184.787,118.942 183.861,72.769 206.391,55.285 207.316,101.458 "/> - </g> - <g> - <polygon fill="#7A88CC" points="183.861,72.769 183.852,72.303 206.382,54.819 206.391,55.285 "/> - </g> - <g> - <polygon fill="#7A88CC" points="184.77,73.385 207.3,55.9 208.207,101.159 185.677,118.644 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="185.677,118.644 184.77,73.385 207.3,55.9 208.207,101.159 "/> - </g> - <g> - <polygon fill="#7684CA" points="183.852,72.303 206.382,54.819 206.832,54.898 184.302,72.383 "/> - </g> - <g> - <polygon fill="#7684CA" points="230.768,127.514 185.246,119.487 207.775,102.002 253.298,110.029 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.677,118.644 208.207,101.159 252.83,109.027 230.3,126.512 "/> - </g> - <g> - <polygon fill="#769AC7" points="230.3,126.512 185.677,118.644 208.207,101.159 252.83,109.027 "/> - </g> - <g> - <polygon fill="#7684CA" points="231.21,127.592 230.768,127.514 253.298,110.029 253.74,110.107 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.201,127.126 253.73,109.642 253.74,110.107 231.21,127.592 "/> - </g> - <g> - <polygon fill="#628CBE" points="229.393,81.253 230.3,126.512 185.677,118.644 184.77,73.385 "/> - </g> - <g> - <polygon fill="#769AC7" points="184.77,73.385 207.3,55.9 251.922,63.769 229.393,81.253 "/> - </g> - <g> - <polygon fill="#7684CA" points="229.393,81.253 184.77,73.385 207.3,55.9 251.922,63.769 "/> - </g> - <g> - <polygon fill="#7A88CC" points="230.3,126.512 229.393,81.253 251.922,63.769 252.83,109.027 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="229.393,81.253 251.922,63.769 252.83,109.027 230.3,126.512 "/> - </g> - <g> - <polygon fill="#7684CA" points="184.302,72.383 206.832,54.898 252.354,62.925 229.824,80.409 "/> - </g> - <g> - <polygon fill="#7A88CC" points="230.275,80.953 252.805,63.469 253.73,109.642 231.201,127.126 "/> - </g> - <g> - <polygon fill="#7684CA" points="229.824,80.409 252.354,62.925 252.795,63.003 230.266,80.487 "/> - </g> - <g> - <polygon fill="#7A88CC" points="230.266,80.487 252.795,63.003 252.805,63.469 230.275,80.953 "/> - </g> - <g> - <path fill="#6272C3" d="M229.824,80.409l0.441,0.078l0.01,0.466l0.925,46.173l0.009,0.466l-0.442-0.078l-45.522-8.027 - l-0.45-0.079l-0.009-0.466l-0.926-46.173l-0.009-0.466l0.45,0.08L229.824,80.409z M230.3,126.512l-0.907-45.259l-44.623-7.868 - l0.907,45.259L230.3,126.512"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M201.234,53.834l0.442,0.078l0.009,0.466l0.926,46.173l0.009,0.465l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.466l-0.925-46.173l-0.009-0.465l0.441,0.078L201.234,53.834z M201.71,99.937l-0.907-45.259 - L156.18,46.81l0.907,45.259L201.71,99.937"/> - </g> - <g> - <polygon fill="#628CBE" points="200.803,54.678 201.71,99.937 157.087,92.068 156.18,46.81 "/> - </g> - <g> - <polygon fill="#7A88CC" points="133.685,110.318 133.676,109.853 156.205,92.368 156.214,92.834 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.126,110.396 133.685,110.318 156.214,92.834 156.656,92.912 "/> - </g> - <g> - <polygon fill="#7A88CC" points="133.676,109.853 132.75,63.68 155.28,46.195 156.205,92.368 "/> - </g> - <g> - <polygon fill="#7A88CC" points="132.75,63.68 132.741,63.214 155.271,45.73 155.28,46.195 "/> - </g> - <g> - <polygon fill="#7A88CC" points="133.65,64.294 156.18,46.81 157.087,92.068 134.558,109.553 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="134.558,109.553 133.65,64.294 156.18,46.81 157.087,92.068 "/> - </g> - <g> - <polygon fill="#7684CA" points="132.741,63.214 155.271,45.73 155.712,45.808 133.182,63.292 "/> - </g> - <g> - <polygon fill="#7684CA" points="179.648,118.423 134.126,110.396 156.656,92.912 202.178,100.938 "/> - </g> - <g> - <polygon fill="#769AC7" points="179.18,117.421 134.558,109.553 157.087,92.068 201.71,99.937 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.558,109.553 157.087,92.068 201.71,99.937 179.18,117.421 "/> - </g> - <g> - <polygon fill="#7684CA" points="180.09,118.5 179.648,118.423 202.178,100.938 202.62,101.016 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.081,118.035 202.611,100.551 202.62,101.016 180.09,118.5 "/> - </g> - <g> - <polygon fill="#628CBE" points="178.273,72.162 179.18,117.421 134.558,109.553 133.65,64.294 "/> - </g> - <g> - <polygon fill="#769AC7" points="133.65,64.294 156.18,46.81 200.803,54.678 178.273,72.162 "/> - </g> - <g> - <polygon fill="#7684CA" points="178.273,72.162 133.65,64.294 156.18,46.81 200.803,54.678 "/> - </g> - <g> - <polygon fill="#7A88CC" points="179.18,117.421 178.273,72.162 200.803,54.678 201.71,99.937 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="178.273,72.162 200.803,54.678 201.71,99.937 179.18,117.421 "/> - </g> - <g> - <polygon fill="#7684CA" points="133.182,63.292 155.712,45.808 201.234,53.834 178.704,71.319 "/> - </g> - <g> - <polygon fill="#7A88CC" points="179.156,71.862 201.685,54.378 202.611,100.551 180.081,118.035 "/> - </g> - <g> - <polygon fill="#7684CA" points="178.704,71.319 201.234,53.834 201.676,53.912 179.146,71.396 "/> - </g> - <g> - <polygon fill="#7A88CC" points="179.146,71.396 201.676,53.912 201.685,54.378 179.156,71.862 "/> - </g> - <g> - <path fill="#6272C3" d="M178.704,71.319l0.442,0.078l0.009,0.466l0.925,46.173l0.009,0.465l-0.442-0.078l-45.522-8.027 - l-0.441-0.078l-0.009-0.465L132.75,63.68l-0.009-0.466l0.441,0.078L178.704,71.319z M179.18,117.421l-0.907-45.259 - l-44.623-7.868l0.907,45.259L179.18,117.421"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M149.853,44.483l0.442,0.078l0.009,0.465L151.23,91.2l0.009,0.465l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.465l-0.925-46.173l-0.009-0.465l0.442,0.078L149.853,44.483z M150.329,90.585l-0.907-45.259 - l-44.623-7.868l0.907,45.259L150.329,90.585"/> - </g> - <g> - <polygon fill="#628CBE" points="149.422,45.327 150.329,90.585 105.707,82.717 104.799,37.458 "/> - </g> - <g> - <polygon fill="#7A88CC" points="82.304,100.967 82.294,100.501 104.824,83.017 104.833,83.482 "/> - </g> - <g> - <polygon fill="#7684CA" points="82.746,101.045 82.304,100.967 104.833,83.482 105.275,83.561 "/> - </g> - <g> - <polygon fill="#7A88CC" points="82.294,100.501 81.369,54.329 103.898,36.844 104.824,83.017 "/> - </g> - <g> - <polygon fill="#7A88CC" points="81.369,54.329 81.359,53.863 103.889,36.379 103.898,36.844 "/> - </g> - <g> - <polygon fill="#7A88CC" points="82.27,54.943 104.799,37.458 105.707,82.717 83.177,100.202 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="83.177,100.202 82.27,54.943 104.799,37.458 105.707,82.717 "/> - </g> - <g> - <polygon fill="#7684CA" points="81.359,53.863 103.889,36.379 104.331,36.457 81.801,53.941 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.268,109.072 82.746,101.045 105.275,83.561 150.797,91.587 "/> - </g> - <g> - <polygon fill="#769AC7" points="127.799,108.07 83.177,100.202 105.707,82.717 150.329,90.585 "/> - </g> - <g> - <polygon fill="#7684CA" points="83.177,100.202 105.707,82.717 150.329,90.585 127.799,108.07 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.709,109.149 128.268,109.072 150.797,91.587 151.239,91.665 "/> - </g> - <g> - <polygon fill="#7A88CC" points="128.7,108.684 151.23,91.2 151.239,91.665 128.709,109.149 "/> - </g> - <g> - <polygon fill="#628CBE" points="126.892,62.811 127.799,108.07 83.177,100.202 82.27,54.943 "/> - </g> - <g> - <polygon fill="#7684CA" points="126.892,62.811 82.27,54.943 104.799,37.458 149.422,45.327 "/> - </g> - <g> - <polygon fill="#769AC7" points="82.27,54.943 104.799,37.458 149.422,45.327 126.892,62.811 "/> - </g> - <g> - <polygon fill="#7A88CC" points="127.799,108.07 126.892,62.811 149.422,45.327 150.329,90.585 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="126.892,62.811 149.422,45.327 150.329,90.585 127.799,108.07 "/> - </g> - <g> - <polygon fill="#7684CA" points="81.801,53.941 104.331,36.457 149.853,44.483 127.323,61.968 "/> - </g> - <g> - <polygon fill="#7A88CC" points="127.775,62.511 150.304,45.027 151.23,91.2 128.7,108.684 "/> - </g> - <g> - <polygon fill="#7684CA" points="127.323,61.968 149.853,44.483 150.295,44.562 127.766,62.046 "/> - </g> - <g> - <polygon fill="#7A88CC" points="127.766,62.046 150.295,44.562 150.304,45.027 127.775,62.511 "/> - </g> - <g> - <path fill="#6272C3" d="M127.323,61.968l0.442,0.078l0.009,0.465l0.925,46.173l0.009,0.465l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.465l-0.926-46.173l-0.009-0.465l0.442,0.078L127.323,61.968z M127.799,108.07l-0.907-45.259 - L82.27,54.943l0.907,45.259L127.799,108.07"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="70.482,215.535 71.39,260.794 26.767,252.926 25.86,207.667 "/> - </g> - <g> - <polygon fill="#7A88CC" points="3.356,271.175 3.347,270.709 25.876,253.225 25.886,253.69 "/> - </g> - <g> - <polygon fill="#7684CA" points="3.806,271.254 3.356,271.175 25.886,253.69 26.336,253.77 "/> - </g> - <g> - <path fill="#6272C3" d="M70.914,214.692l0.442,0.078l0.009,0.457l0.926,46.181l0.009,0.466l-0.442-0.078l-45.522-8.026 - l-0.45-0.079l-0.009-0.466l-0.926-46.182l-0.009-0.457l0.45,0.079L70.914,214.692z M71.39,260.794l-0.907-45.259 - l-44.623-7.868l0.907,45.259L71.39,260.794"/> - </g> - <g> - <polygon fill="#7A88CC" points="3.347,270.709 2.421,224.527 24.951,207.043 25.876,253.225 "/> - </g> - <g> - <polygon fill="#7A88CC" points="2.421,224.527 2.412,224.07 24.941,206.586 24.951,207.043 "/> - </g> - <g> - <polygon fill="#7A88CC" points="3.33,225.151 25.86,207.667 26.767,252.926 4.237,270.41 "/> - </g> - <g> - <polygon fill="#FFDD77" points="4.237,270.41 3.33,225.151 25.86,207.667 26.767,252.926 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.412,224.07 24.941,206.586 25.392,206.665 2.862,224.149 "/> - </g> - <g> - <polygon fill="#7684CA" points="49.328,279.28 3.806,271.254 26.336,253.77 71.858,261.796 "/> - </g> - <g> - <polygon fill="#FFDC72" points="48.86,278.278 4.237,270.41 26.767,252.926 71.39,260.794 "/> - </g> - <g> - <polygon fill="#7684CA" points="4.237,270.41 26.767,252.926 71.39,260.794 48.86,278.278 "/> - </g> - <g> - <polygon fill="#7684CA" points="49.77,279.358 49.328,279.28 71.858,261.796 72.3,261.874 "/> - </g> - <g> - <polygon fill="#7A88CC" points="49.761,278.893 72.291,261.408 72.3,261.874 49.77,279.358 "/> - </g> - <g> - <polygon fill="#7684CA" points="47.953,233.02 3.33,225.151 25.86,207.667 70.482,215.535 "/> - </g> - <g> - <polygon fill="#FFDC72" points="3.33,225.151 25.86,207.667 70.482,215.535 47.953,233.02 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.86,278.278 47.953,233.02 70.482,215.535 71.39,260.794 "/> - </g> - <g> - <polygon fill="#FFDD77" points="47.953,233.02 70.482,215.535 71.39,260.794 48.86,278.278 "/> - </g> - <g> - <path fill="#6272C3" d="M48.384,232.177l0.442,0.078l0.009,0.457l0.926,46.181l0.009,0.466l-0.442-0.078l-45.522-8.026 - l-0.45-0.079l-0.009-0.466l-0.926-46.182l-0.009-0.457l0.45,0.079L48.384,232.177z M48.86,278.278l-0.907-45.259L3.33,225.151 - l0.907,45.259L48.86,278.278"/> - </g> - <g> - <polygon fill="#FFD65D" points="47.953,233.02 48.86,278.278 4.237,270.41 3.33,225.151 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.862,224.149 25.392,206.665 70.914,214.692 48.384,232.177 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.835,232.712 71.365,215.228 72.291,261.408 49.761,278.893 "/> - </g> - <g> - <polygon fill="#7684CA" points="48.384,232.177 70.914,214.692 71.355,214.771 48.826,232.255 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.826,232.255 71.355,214.771 71.365,215.228 48.835,232.712 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="223.365,243.327 224.272,288.586 179.642,280.717 178.735,235.458 "/> - </g> - <g> - <polygon fill="#7A88CC" points="156.239,298.966 156.23,298.501 178.759,281.017 178.769,281.482 "/> - </g> - <g> - <polygon fill="#7684CA" points="156.689,299.046 156.239,298.966 178.769,281.482 179.219,281.562 "/> - </g> - <g> - <polygon fill="#7A88CC" points="156.23,298.501 155.304,252.328 177.834,234.844 178.759,281.017 "/> - </g> - <g> - <polygon fill="#7A88CC" points="156.205,252.942 178.734,235.458 179.642,280.717 157.112,298.201 "/> - </g> - <g> - <polygon fill="#FFDD77" points="157.112,298.201 156.205,252.942 178.735,235.458 179.642,280.717 "/> - </g> - <g> - <polygon fill="#6272C3" points="225.173,289.2 224.248,243.027 224.238,242.563 223.789,242.482 178.274,234.457 - 177.824,234.378 177.834,234.844 177.86,236.137 178.734,235.458 223.365,243.327 224.272,288.586 179.642,280.717 - 178.767,281.396 178.769,281.482 179.219,281.562 224.733,289.587 225.183,289.666 "/> - </g> - <g> - <polygon fill="#7A88CC" points="155.304,252.328 155.295,251.862 177.824,234.378 177.834,234.844 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.295,251.862 177.824,234.378 178.274,234.457 155.745,251.941 "/> - </g> - <g> - <polygon fill="#7684CA" points="202.203,307.071 156.689,299.046 179.219,281.562 224.733,289.587 "/> - </g> - <g> - <polygon fill="#FFDC72" points="201.743,306.07 157.112,298.201 179.642,280.717 224.272,288.586 "/> - </g> - <g> - <polygon fill="#7684CA" points="157.112,298.201 179.642,280.717 224.272,288.586 201.743,306.07 "/> - </g> - <g> - <polygon fill="#7684CA" points="202.653,307.15 202.203,307.071 224.733,289.587 225.183,289.666 "/> - </g> - <g> - <polygon fill="#7A88CC" points="202.644,306.685 225.173,289.2 225.183,289.666 202.653,307.15 "/> - </g> - <g> - <polygon fill="#FFD65D" points="200.835,260.812 201.743,306.07 157.112,298.201 156.205,252.942 "/> - </g> - <g> - <polygon fill="#7684CA" points="200.835,260.812 156.205,252.942 178.734,235.458 223.365,243.327 "/> - </g> - <g> - <polygon fill="#FFDC72" points="156.205,252.942 178.735,235.458 223.365,243.327 200.835,260.812 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.743,306.07 200.835,260.812 223.365,243.327 224.272,288.586 "/> - </g> - <g> - <polygon fill="#FFDD77" points="200.835,260.812 223.365,243.327 224.272,288.586 201.743,306.07 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.745,251.941 178.274,234.457 223.789,242.482 201.259,259.967 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.718,260.512 224.248,243.027 225.173,289.2 202.644,306.685 "/> - </g> - <g> - <polygon fill="#7684CA" points="201.259,259.967 223.789,242.482 224.238,242.562 201.709,260.046 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.709,260.046 224.238,242.562 224.248,243.027 201.718,260.512 "/> - </g> - <g> - <path fill="#6272C3" d="M201.259,259.967l0.45,0.079l0.009,0.466l0.925,46.173l0.009,0.466l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.465l-0.926-46.173l-0.009-0.466l0.45,0.079L201.259,259.967z M201.743,306.07l-0.907-45.259 - l-44.63-7.869l0.907,45.259L201.743,306.07"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="172.246,234.237 173.153,279.495 128.531,271.628 127.624,226.369 "/> - </g> - <g> - <polygon fill="#7A88CC" points="105.128,289.877 105.119,289.412 127.648,271.928 127.657,272.393 "/> - </g> - <g> - <polygon fill="#7684CA" points="105.57,289.955 105.128,289.877 127.657,272.393 128.1,272.471 "/> - </g> - <g> - <path fill="#6272C3" d="M172.677,233.394l0.441,0.078l0.009,0.465l0.926,46.174l0.009,0.465l-0.441-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.465l-0.925-46.174l-0.009-0.465l0.442,0.078L172.677,233.394z M173.153,279.495l-0.907-45.258 - l-44.623-7.868l0.908,45.259L173.153,279.495"/> - </g> - <g> - <polygon fill="#7A88CC" points="105.119,289.412 104.193,243.238 126.723,225.754 127.648,271.928 "/> - </g> - <g> - <polygon fill="#7A88CC" points="104.193,243.238 104.184,242.773 126.713,225.289 126.723,225.754 "/> - </g> - <g> - <polygon fill="#7A88CC" points="105.094,243.854 127.623,226.369 128.531,271.628 106.001,289.111 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="106.001,289.111 105.094,243.854 127.624,226.369 128.531,271.628 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.184,242.773 126.713,225.289 127.155,225.367 104.625,242.852 "/> - </g> - <g> - <polygon fill="#7684CA" points="151.092,297.981 105.57,289.955 128.1,272.471 173.622,280.497 "/> - </g> - <g> - <polygon fill="#7684CA" points="106.001,289.111 128.531,271.628 173.153,279.495 150.624,296.979 "/> - </g> - <g> - <polygon fill="#769AC7" points="150.624,296.979 106.001,289.111 128.531,271.628 173.153,279.495 "/> - </g> - <g> - <polygon fill="#7684CA" points="151.533,298.06 151.092,297.981 173.622,280.497 174.063,280.575 "/> - </g> - <g> - <polygon fill="#7A88CC" points="151.524,297.595 174.054,280.11 174.063,280.575 151.533,298.06 "/> - </g> - <g> - <polygon fill="#628CBE" points="149.716,251.722 150.624,296.979 106.001,289.111 105.094,243.854 "/> - </g> - <g> - <polygon fill="#7684CA" points="149.716,251.722 105.094,243.854 127.623,226.369 172.246,234.237 "/> - </g> - <g> - <polygon fill="#769AC7" points="105.094,243.854 127.624,226.369 172.246,234.237 149.716,251.722 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="149.716,251.722 172.246,234.237 173.153,279.495 150.624,296.979 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.624,296.979 149.716,251.722 172.246,234.237 173.153,279.495 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.625,242.852 127.155,225.367 172.677,233.394 150.147,250.878 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.599,251.421 173.128,233.937 174.054,280.11 151.524,297.595 "/> - </g> - <g> - <polygon fill="#7684CA" points="150.147,250.878 172.677,233.394 173.119,233.472 150.589,250.956 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.589,250.956 173.119,233.472 173.128,233.937 150.599,251.421 "/> - </g> - <g> - <path fill="#6272C3" d="M150.147,250.878l0.441,0.078l0.01,0.465l0.925,46.174l0.009,0.465l-0.441-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.465l-0.926-46.174l-0.009-0.465l0.442,0.078L150.147,250.878z M150.624,296.979l-0.907-45.258 - l-44.623-7.868l0.907,45.258L150.624,296.979"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="120.864,224.887 121.771,270.146 77.149,262.277 76.242,217.019 "/> - </g> - <g> - <polygon fill="#7A88CC" points="53.738,280.526 53.729,280.061 76.258,262.576 76.268,263.042 "/> - </g> - <g> - <polygon fill="#7684CA" points="54.188,280.605 53.738,280.526 76.268,263.042 76.718,263.121 "/> - </g> - <g> - <path fill="#6272C3" d="M121.295,224.044l0.442,0.078l0.009,0.465l0.926,46.173l0.009,0.466l-0.442-0.078l-45.522-8.026 - l-0.45-0.079l-0.009-0.466l-0.925-46.173l-0.01-0.466l0.45,0.079L121.295,224.044z M121.771,270.146l-0.907-45.259 - l-44.623-7.868l0.907,45.259L121.771,270.146"/> - </g> - <g> - <polygon fill="#7A88CC" points="53.729,280.061 52.803,233.888 75.333,216.403 76.258,262.576 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.803,233.888 52.794,233.422 75.323,215.938 75.333,216.403 "/> - </g> - <g> - <polygon fill="#7A88CC" points="53.712,234.503 76.242,217.019 77.149,262.277 54.619,279.762 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="54.619,279.762 53.712,234.503 76.242,217.019 77.149,262.277 "/> - </g> - <g> - <polygon fill="#7684CA" points="52.794,233.422 75.323,215.938 75.773,216.017 53.244,233.501 "/> - </g> - <g> - <polygon fill="#7684CA" points="99.71,288.632 54.188,280.605 76.718,263.121 122.24,271.147 "/> - </g> - <g> - <polygon fill="#769AC7" points="99.242,287.63 54.619,279.762 77.149,262.277 121.771,270.146 "/> - </g> - <g> - <polygon fill="#7684CA" points="54.619,279.762 77.149,262.277 121.771,270.146 99.242,287.63 "/> - </g> - <g> - <polygon fill="#7684CA" points="100.152,288.71 99.71,288.632 122.24,271.147 122.682,271.226 "/> - </g> - <g> - <polygon fill="#7A88CC" points="100.143,288.244 122.672,270.76 122.682,271.226 100.152,288.71 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.334,242.371 53.712,234.503 76.242,217.019 120.864,224.887 "/> - </g> - <g> - <polygon fill="#769AC7" points="53.712,234.503 76.242,217.019 120.864,224.887 98.334,242.371 "/> - </g> - <g> - <polygon fill="#7A88CC" points="99.242,287.63 98.334,242.371 120.864,224.887 121.771,270.146 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="98.334,242.371 120.864,224.887 121.771,270.146 99.242,287.63 "/> - </g> - <g> - <path fill="#6272C3" d="M98.766,241.528l0.442,0.077l0.009,0.466l0.925,46.173l0.009,0.466l-0.442-0.078l-45.522-8.026 - l-0.45-0.079l-0.009-0.466l-0.926-46.173l-0.009-0.466l0.45,0.079L98.766,241.528z M99.242,287.63l-0.907-45.259 - l-44.623-7.868l0.907,45.259L99.242,287.63"/> - </g> - <g> - <polygon fill="#628CBE" points="98.334,242.371 99.242,287.63 54.619,279.762 53.712,234.503 "/> - </g> - <g> - <polygon fill="#7684CA" points="53.244,233.501 75.773,216.017 121.295,224.044 98.766,241.528 "/> - </g> - <g> - <polygon fill="#7A88CC" points="99.217,242.071 121.747,224.587 122.672,270.76 100.143,288.244 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.766,241.528 121.295,224.044 121.737,224.122 99.208,241.605 "/> - </g> - <g> - <polygon fill="#7A88CC" points="99.208,241.605 121.737,224.122 121.747,224.587 99.217,242.071 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="69.483,164.533 70.391,209.791 25.76,201.922 24.853,156.663 "/> - </g> - <g> - <polygon fill="#7A88CC" points="2.357,220.171 2.348,219.706 24.877,202.222 24.887,202.687 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.807,220.251 2.357,220.171 24.887,202.687 25.337,202.767 "/> - </g> - <g> - <path fill="#6272C3" d="M69.907,163.688l0.45,0.079l0.009,0.465l0.926,46.173l0.009,0.466l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.465l-0.925-46.173l-0.009-0.465l0.45,0.08L69.907,163.688z M70.391,209.791l-0.907-45.258l-44.631-7.87 - l0.908,45.259L70.391,209.791"/> - </g> - <g> - <polygon fill="#7A88CC" points="2.348,219.706 1.422,173.533 23.952,156.048 24.877,202.222 "/> - </g> - <g> - <polygon fill="#7A88CC" points="1.422,173.533 1.413,173.067 23.943,155.583 23.952,156.048 "/> - </g> - <g> - <polygon fill="#7A88CC" points="2.323,174.147 24.853,156.663 25.76,201.922 3.23,219.406 "/> - </g> - <g> - <polygon fill="#FFDD77" points="3.23,219.406 2.323,174.147 24.853,156.663 25.76,201.922 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.413,173.067 23.943,155.583 24.393,155.663 1.863,173.146 "/> - </g> - <g> - <polygon fill="#7684CA" points="48.321,228.276 2.807,220.251 25.337,202.767 70.851,210.792 "/> - </g> - <g> - <polygon fill="#FFDC72" points="47.861,227.275 3.23,219.406 25.76,201.922 70.391,209.791 "/> - </g> - <g> - <polygon fill="#7684CA" points="3.23,219.406 25.76,201.922 70.391,209.791 47.861,227.275 "/> - </g> - <g> - <polygon fill="#7684CA" points="48.771,228.355 48.321,228.276 70.851,210.792 71.301,210.871 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.762,227.89 71.292,210.405 71.301,210.871 48.771,228.355 "/> - </g> - <g> - <polygon fill="#FFD65D" points="46.954,182.017 47.861,227.275 3.23,219.406 2.323,174.147 "/> - </g> - <g> - <polygon fill="#FFDC72" points="2.323,174.147 24.853,156.663 69.483,164.533 46.954,182.017 "/> - </g> - <g> - <polygon fill="#7684CA" points="46.954,182.017 2.323,174.147 24.853,156.663 69.483,164.533 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.861,227.275 46.954,182.017 69.483,164.533 70.391,209.791 "/> - </g> - <g> - <polygon fill="#FFDD77" points="46.954,182.017 69.483,164.533 70.391,209.791 47.861,227.275 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.863,173.146 24.393,155.663 69.907,163.688 47.377,181.172 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.836,181.717 70.366,164.232 71.292,210.405 48.762,227.89 "/> - </g> - <g> - <polygon fill="#7684CA" points="47.377,181.172 69.907,163.688 70.356,163.767 47.827,181.251 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.827,181.251 70.356,163.767 70.366,164.232 47.836,181.717 "/> - </g> - <g> - <path fill="#6272C3" d="M47.377,181.172l0.45,0.08l0.009,0.465l0.925,46.173l0.009,0.466l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.465l-0.926-46.173l-0.009-0.465l0.45,0.079L47.377,181.172z M47.861,227.275l-0.907-45.259l-44.63-7.869 - l0.907,45.259L47.861,227.275"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="222.366,192.333 223.273,237.583 178.643,229.714 177.736,184.463 "/> - </g> - <g> - <polygon fill="#7A88CC" points="155.24,247.964 155.231,247.506 177.761,230.021 177.77,230.479 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.69,248.043 155.24,247.964 177.77,230.479 178.22,230.559 "/> - </g> - <g> - <path fill="#6272C3" d="M222.79,191.48l0.45,0.079l0.009,0.465l0.926,46.182l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.01-0.465l0.45,0.08L222.79,191.48z M223.273,237.583l-0.907-45.25l-44.63-7.87 - l0.907,45.251L223.273,237.583"/> - </g> - <g> - <polygon fill="#7A88CC" points="155.231,247.506 154.305,201.325 176.835,183.84 177.761,230.021 "/> - </g> - <g> - <polygon fill="#7A88CC" points="154.305,201.325 154.296,200.859 176.825,183.375 176.835,183.84 "/> - </g> - <g> - <polygon fill="#7A88CC" points="155.206,201.947 177.736,184.463 178.643,229.714 156.113,247.198 "/> - </g> - <g> - <polygon fill="#FFDD77" points="156.113,247.198 155.206,201.947 177.736,184.463 178.643,229.714 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.296,200.859 176.825,183.375 177.275,183.455 154.746,200.938 "/> - </g> - <g> - <polygon fill="#7684CA" points="201.204,256.068 155.69,248.043 178.22,230.559 223.734,238.584 "/> - </g> - <g> - <polygon fill="#7684CA" points="156.113,247.198 178.643,229.714 223.273,237.583 200.744,255.067 "/> - </g> - <g> - <polygon fill="#FFDC72" points="200.744,255.067 156.113,247.198 178.643,229.714 223.273,237.583 "/> - </g> - <g> - <polygon fill="#7684CA" points="201.654,256.147 201.204,256.068 223.734,238.584 224.184,238.663 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.645,255.69 224.174,238.206 224.184,238.663 201.654,256.147 "/> - </g> - <g> - <polygon fill="#7684CA" points="199.837,209.817 155.206,201.947 177.736,184.463 222.366,192.333 "/> - </g> - <g> - <polygon fill="#FFD65D" points="199.837,209.817 200.744,255.067 156.113,247.198 155.206,201.947 "/> - </g> - <g> - <polygon fill="#FFDC72" points="155.206,201.947 177.736,184.463 222.366,192.333 199.837,209.817 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.744,255.067 199.837,209.817 222.366,192.333 223.273,237.583 "/> - </g> - <g> - <polygon fill="#FFDD77" points="199.837,209.817 222.366,192.333 223.273,237.583 200.744,255.067 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.746,200.938 177.275,183.455 222.79,191.48 200.26,208.964 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.719,209.509 223.249,192.024 224.174,238.206 201.645,255.69 "/> - </g> - <g> - <polygon fill="#7684CA" points="200.26,208.964 222.79,191.48 223.239,191.559 200.71,209.044 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.71,209.044 223.239,191.559 223.249,192.024 200.719,209.509 "/> - </g> - <g> - <path fill="#6272C3" d="M200.26,208.964l0.45,0.08l0.009,0.465l0.926,46.182l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.466l0.45,0.079L200.26,208.964z M200.744,255.067l-0.907-45.25 - l-44.631-7.87l0.907,45.251L200.744,255.067"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M171.678,182.399l0.442,0.078l0.009,0.457l0.926,46.181l0.009,0.458l-0.442-0.078l-45.522-8.027 - l-0.442-0.077l-0.009-0.458l-0.926-46.181l-0.009-0.457l0.442,0.078L171.678,182.399z M172.154,228.493l-0.907-45.251 - l-44.623-7.868l0.907,45.251L172.154,228.493"/> - </g> - <g> - <polygon fill="#FFD65D" points="171.247,183.242 172.154,228.493 127.531,220.625 126.625,175.374 "/> - </g> - <g> - <polygon fill="#7A88CC" points="104.129,238.875 104.12,238.417 126.649,220.933 126.658,221.391 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.571,238.952 104.129,238.875 126.658,221.391 127.1,221.468 "/> - </g> - <g> - <polygon fill="#7A88CC" points="104.12,238.417 103.194,192.236 125.724,174.751 126.649,220.933 "/> - </g> - <g> - <polygon fill="#7A88CC" points="103.194,192.236 103.185,191.779 125.714,174.294 125.724,174.751 "/> - </g> - <g> - <polygon fill="#7A88CC" points="104.095,192.858 126.625,175.374 127.531,220.625 105.002,238.109 "/> - </g> - <g> - <polygon fill="#FFDD77" points="105.002,238.109 104.095,192.858 126.625,175.374 127.531,220.625 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.185,191.779 125.714,174.294 126.156,174.372 103.626,191.856 "/> - </g> - <g> - <polygon fill="#7684CA" points="150.093,246.979 104.571,238.952 127.1,221.468 172.623,229.495 "/> - </g> - <g> - <polygon fill="#FFDC72" points="149.625,245.978 105.002,238.109 127.531,220.625 172.154,228.493 "/> - </g> - <g> - <polygon fill="#7684CA" points="105.002,238.109 127.531,220.625 172.154,228.493 149.625,245.978 "/> - </g> - <g> - <polygon fill="#7684CA" points="150.535,247.058 150.093,246.979 172.623,229.495 173.064,229.573 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.525,246.6 173.055,229.115 173.064,229.573 150.535,247.058 "/> - </g> - <g> - <polygon fill="#FFDC72" points="104.095,192.858 126.625,175.374 171.247,183.242 148.717,200.727 "/> - </g> - <g> - <polygon fill="#7684CA" points="148.717,200.727 104.095,192.858 126.625,175.374 171.247,183.242 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.625,245.978 148.717,200.727 171.247,183.242 172.154,228.493 "/> - </g> - <g> - <polygon fill="#FFDD77" points="148.717,200.727 171.247,183.242 172.154,228.493 149.625,245.978 "/> - </g> - <g> - <path fill="#6272C3" d="M149.149,199.883l0.442,0.078l0.009,0.458l0.926,46.181l0.009,0.458l-0.442-0.078l-45.522-8.027 - l-0.442-0.077l-0.009-0.458l-0.926-46.181l-0.009-0.457l0.442,0.078L149.149,199.883z M149.625,245.978l-0.907-45.251 - l-44.623-7.868l0.907,45.251L149.625,245.978"/> - </g> - <g> - <polygon fill="#FFD65D" points="148.717,200.727 149.625,245.978 105.002,238.109 104.095,192.858 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.626,191.856 126.156,174.372 171.678,182.399 149.149,199.883 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.6,200.419 172.129,182.934 173.055,229.115 150.525,246.6 "/> - </g> - <g> - <polygon fill="#7684CA" points="149.149,199.883 171.678,182.399 172.12,182.477 149.591,199.961 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.591,199.961 172.12,182.477 172.129,182.934 149.6,200.419 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="119.865,173.884 120.772,219.143 76.142,211.273 75.235,166.014 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.739,229.522 52.73,229.057 75.259,211.572 75.269,212.038 "/> - </g> - <g> - <polygon fill="#7684CA" points="53.189,229.603 52.739,229.522 75.269,212.038 75.719,212.118 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.73,229.057 51.804,182.884 74.334,165.399 75.259,211.572 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.705,183.499 75.234,166.014 76.142,211.273 53.612,228.758 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="53.612,228.758 52.705,183.499 75.235,166.014 76.142,211.273 "/> - </g> - <g> - <polygon fill="#6272C3" points="121.673,219.757 120.748,173.583 120.738,173.118 120.289,173.039 74.774,165.014 - 74.324,164.934 74.334,165.399 74.36,166.693 75.234,166.014 119.865,173.884 120.772,219.143 76.142,211.273 75.267,211.952 - 75.269,212.038 75.719,212.118 121.233,220.144 121.683,220.223 "/> - </g> - <g> - <polygon fill="#7A88CC" points="51.804,182.884 51.795,182.418 74.324,164.934 74.334,165.399 "/> - </g> - <g> - <polygon fill="#7684CA" points="51.795,182.418 74.324,164.934 74.774,165.014 52.245,182.498 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.703,237.628 53.189,229.603 75.719,212.118 121.233,220.144 "/> - </g> - <g> - <polygon fill="#769AC7" points="98.243,236.627 53.612,228.758 76.142,211.273 120.772,219.143 "/> - </g> - <g> - <polygon fill="#7684CA" points="53.612,228.758 76.142,211.273 120.772,219.143 98.243,236.627 "/> - </g> - <g> - <polygon fill="#7684CA" points="99.153,237.707 98.703,237.628 121.233,220.144 121.683,220.223 "/> - </g> - <g> - <polygon fill="#7A88CC" points="99.144,237.24 121.673,219.757 121.683,220.223 99.153,237.707 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.335,191.368 52.705,183.499 75.234,166.014 119.865,173.884 "/> - </g> - <g> - <polygon fill="#769AC7" points="52.705,183.499 75.235,166.014 119.865,173.884 97.335,191.368 "/> - </g> - <g> - <polygon fill="#7A88CC" points="98.243,236.627 97.335,191.368 119.865,173.884 120.772,219.143 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="97.335,191.368 119.865,173.884 120.772,219.143 98.243,236.627 "/> - </g> - <g> - <path fill="#6272C3" d="M97.759,190.523l0.45,0.079l0.009,0.465l0.925,46.172l0.009,0.467l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.466l-0.926-46.173l-0.009-0.465l0.45,0.08L97.759,190.523z M98.243,236.627l-0.907-45.259l-44.63-7.87 - l0.907,45.259L98.243,236.627"/> - </g> - <g> - <polygon fill="#628CBE" points="97.335,191.368 98.243,236.627 53.612,228.758 52.705,183.499 "/> - </g> - <g> - <polygon fill="#7684CA" points="52.245,182.498 74.774,165.014 120.289,173.039 97.759,190.523 "/> - </g> - <g> - <polygon fill="#7A88CC" points="98.218,191.068 120.748,173.583 121.673,219.757 99.144,237.24 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.759,190.523 120.289,173.039 120.738,173.118 98.209,190.603 "/> - </g> - <g> - <polygon fill="#7A88CC" points="98.209,190.603 120.738,173.118 120.748,173.583 98.218,191.068 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="69.069,113.359 69.977,158.609 25.354,150.741 24.447,105.491 "/> - </g> - <g> - <polygon fill="#7A88CC" points="1.943,168.998 1.934,168.532 24.463,151.048 24.473,151.513 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.393,169.077 1.943,168.998 24.473,151.513 24.923,151.593 "/> - </g> - <g> - <path fill="#6272C3" d="M69.5,112.516l0.442,0.078l0.009,0.458l0.926,46.181l0.009,0.465l-0.442-0.078l-45.522-8.026 - l-0.45-0.08l-0.009-0.465l-0.926-46.181l-0.009-0.458l0.45,0.08L69.5,112.516z M69.977,158.609l-0.907-45.25l-44.623-7.868 - l0.907,45.25L69.977,158.609"/> - </g> - <g> - <polygon fill="#7A88CC" points="1.934,168.532 1.008,122.351 23.538,104.867 24.463,151.048 "/> - </g> - <g> - <polygon fill="#7A88CC" points="1.008,122.351 0.999,121.894 23.529,104.409 23.538,104.867 "/> - </g> - <g> - <polygon fill="#7A88CC" points="1.917,122.975 24.447,105.491 25.354,150.741 2.824,168.226 "/> - </g> - <g> - <polygon fill="#FFDD77" points="2.824,168.226 1.917,122.975 24.447,105.491 25.354,150.741 "/> - </g> - <g> - <polygon fill="#7684CA" points="0.999,121.894 23.529,104.409 23.979,104.489 1.449,121.973 "/> - </g> - <g> - <polygon fill="#7684CA" points="47.915,177.104 2.393,169.077 24.923,151.593 70.445,159.619 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.824,168.226 25.354,150.741 69.977,158.609 47.447,176.094 "/> - </g> - <g> - <polygon fill="#FFDC72" points="47.447,176.094 2.824,168.226 25.354,150.741 69.977,158.609 "/> - </g> - <g> - <polygon fill="#7684CA" points="48.357,177.182 47.915,177.104 70.445,159.619 70.887,159.697 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.348,176.716 70.877,159.232 70.887,159.697 48.357,177.182 "/> - </g> - <g> - <polygon fill="#FFDC72" points="1.917,122.975 24.447,105.491 69.069,113.359 46.54,130.843 "/> - </g> - <g> - <polygon fill="#7684CA" points="46.54,130.843 1.917,122.975 24.447,105.491 69.069,113.359 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.447,176.094 46.54,130.843 69.069,113.359 69.977,158.609 "/> - </g> - <g> - <polygon fill="#FFDD77" points="46.54,130.843 69.069,113.359 69.977,158.609 47.447,176.094 "/> - </g> - <g> - <path fill="#6272C3" d="M46.971,130l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.465l-0.442-0.078l-45.522-8.027 - l-0.45-0.079l-0.009-0.465l-0.926-46.181l-0.009-0.458l0.45,0.08L46.971,130z M47.447,176.094l-0.907-45.251l-44.623-7.868 - l0.907,45.251L47.447,176.094"/> - </g> - <g> - <polygon fill="#FFD65D" points="46.54,130.843 47.447,176.094 2.824,168.226 1.917,122.975 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.449,121.973 23.979,104.489 69.5,112.516 46.971,130 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.422,130.535 69.952,113.051 70.877,159.232 48.348,176.716 "/> - </g> - <g> - <polygon fill="#7684CA" points="46.971,130 69.5,112.516 69.942,112.593 47.413,130.078 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.413,130.078 69.942,112.593 69.952,113.051 47.422,130.535 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="221.952,141.151 222.859,186.409 178.229,178.54 177.322,133.281 "/> - </g> - <g> - <polygon fill="#7A88CC" points="154.826,196.79 154.817,196.324 177.346,178.84 177.355,179.305 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.276,196.869 154.826,196.79 177.355,179.305 177.806,179.385 "/> - </g> - <g> - <path fill="#6272C3" d="M222.384,140.308l0.441,0.078l0.009,0.465l0.926,46.173l0.009,0.465l-0.441-0.078l-45.522-8.026 - l-0.45-0.08l-0.009-0.465l-0.925-46.173l-0.01-0.465l0.451,0.08L222.384,140.308z M222.859,186.409l-0.907-45.258 - l-44.631-7.87l0.908,45.259L222.859,186.409"/> - </g> - <g> - <polygon fill="#7A88CC" points="154.817,196.324 153.891,150.151 176.421,132.667 177.346,178.84 "/> - </g> - <g> - <polygon fill="#7A88CC" points="153.891,150.151 153.882,149.686 176.411,132.201 176.421,132.667 "/> - </g> - <g> - <polygon fill="#7A88CC" points="154.792,150.766 177.321,133.281 178.229,178.54 155.699,196.024 "/> - </g> - <g> - <polygon fill="#FFDD77" points="155.699,196.024 154.792,150.766 177.322,133.281 178.229,178.54 "/> - </g> - <g> - <polygon fill="#7684CA" points="153.882,149.686 176.411,132.201 176.862,132.281 154.332,149.765 "/> - </g> - <g> - <polygon fill="#7684CA" points="200.798,204.896 155.276,196.869 177.806,179.385 223.328,187.411 "/> - </g> - <g> - <polygon fill="#FFDC72" points="200.33,203.894 155.699,196.024 178.229,178.54 222.859,186.409 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.699,196.024 178.229,178.54 222.859,186.409 200.33,203.894 "/> - </g> - <g> - <polygon fill="#7684CA" points="201.24,204.974 200.798,204.896 223.328,187.411 223.77,187.489 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.23,204.508 223.76,187.024 223.77,187.489 201.24,204.974 "/> - </g> - <g> - <polygon fill="#FFD65D" points="199.422,158.635 200.33,203.894 155.699,196.024 154.792,150.766 "/> - </g> - <g> - <polygon fill="#7684CA" points="199.422,158.635 154.792,150.766 177.321,133.281 221.952,141.151 "/> - </g> - <g> - <polygon fill="#FFDC72" points="154.792,150.766 177.322,133.281 221.952,141.151 199.422,158.635 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.33,203.894 199.422,158.635 221.952,141.151 222.859,186.409 "/> - </g> - <g> - <polygon fill="#FFDD77" points="199.422,158.635 221.952,141.151 222.859,186.409 200.33,203.894 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.332,149.765 176.862,132.281 222.384,140.308 199.854,157.792 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.305,158.335 222.834,140.851 223.76,187.024 201.23,204.508 "/> - </g> - <g> - <polygon fill="#7684CA" points="199.854,157.792 222.384,140.308 222.825,140.385 200.296,157.87 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.296,157.87 222.825,140.385 222.834,140.851 200.305,158.335 "/> - </g> - <g> - <path fill="#6272C3" d="M199.854,157.792l0.442,0.078l0.009,0.465l0.925,46.173l0.009,0.466l-0.441-0.078l-45.522-8.026 - l-0.45-0.08l-0.009-0.465l-0.926-46.173l-0.009-0.465l0.45,0.08L199.854,157.792z M200.33,203.894l-0.907-45.259l-44.63-7.869 - l0.907,45.258L200.33,203.894"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M171.264,131.216l0.442,0.078l0.009,0.466l0.926,46.181l0.009,0.457l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.457l-0.926-46.181l-0.009-0.466l0.442,0.078L171.264,131.216z M171.74,177.319l-0.907-45.25 - L126.21,124.2l0.907,45.25L171.74,177.319"/> - </g> - <g> - <polygon fill="#628CBE" points="170.833,132.068 171.74,177.319 127.118,169.451 126.21,124.2 "/> - </g> - <g> - <polygon fill="#7A88CC" points="103.715,187.7 103.706,187.243 126.235,169.759 126.244,170.216 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.157,187.778 103.715,187.7 126.244,170.216 126.687,170.294 "/> - </g> - <g> - <polygon fill="#7A88CC" points="103.706,187.243 102.78,141.062 125.31,123.578 126.235,169.759 "/> - </g> - <g> - <polygon fill="#7A88CC" points="102.78,141.062 102.771,140.596 125.3,123.112 125.31,123.578 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="104.588,186.935 103.681,141.685 126.21,124.2 127.118,169.451 "/> - </g> - <g> - <polygon fill="#7A88CC" points="103.681,141.685 126.21,124.2 127.118,169.451 104.588,186.935 "/> - </g> - <g> - <polygon fill="#7684CA" points="102.771,140.596 125.3,123.112 125.742,123.189 103.212,140.674 "/> - </g> - <g> - <polygon fill="#7684CA" points="149.679,195.805 104.157,187.778 126.687,170.294 172.208,178.321 "/> - </g> - <g> - <polygon fill="#769AC7" points="149.21,194.803 104.588,186.935 127.118,169.451 171.74,177.319 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.588,186.935 127.118,169.451 171.74,177.319 149.21,194.803 "/> - </g> - <g> - <polygon fill="#7684CA" points="150.121,195.883 149.679,195.805 172.208,178.321 172.65,178.398 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.112,195.426 172.641,177.941 172.65,178.398 150.121,195.883 "/> - </g> - <g> - <polygon fill="#628CBE" points="148.304,149.553 149.21,194.803 104.588,186.935 103.681,141.685 "/> - </g> - <g> - <polygon fill="#7684CA" points="148.304,149.553 103.681,141.685 126.21,124.2 170.833,132.068 "/> - </g> - <g> - <polygon fill="#769AC7" points="103.681,141.685 126.21,124.2 170.833,132.068 148.304,149.553 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.21,194.803 148.304,149.553 170.833,132.068 171.74,177.319 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="148.304,149.553 170.833,132.068 171.74,177.319 149.21,194.803 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.212,140.674 125.742,123.189 171.264,131.216 148.734,148.701 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.186,149.245 171.715,131.76 172.641,177.941 150.112,195.426 "/> - </g> - <g> - <polygon fill="#7684CA" points="148.734,148.701 171.264,131.216 171.706,131.294 149.177,148.778 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.177,148.778 171.706,131.294 171.715,131.76 149.186,149.245 "/> - </g> - <g> - <path fill="#6272C3" d="M148.734,148.701l0.442,0.078l0.009,0.466l0.926,46.181l0.009,0.457l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.457l-0.926-46.181l-0.009-0.466l0.442,0.078L148.734,148.701z M149.21,194.803l-0.907-45.25 - l-44.623-7.868l0.907,45.25L149.21,194.803"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M119.883,121.866l0.441,0.078l0.009,0.466l0.926,46.173l0.009,0.466l-0.441-0.078l-45.523-8.027 - l-0.441-0.078l-0.009-0.466l-0.925-46.173l-0.01-0.465l0.442,0.078L119.883,121.866z M120.359,167.968l-0.907-45.259 - l-44.623-7.868l0.908,45.259L120.359,167.968"/> - </g> - <g> - <polygon fill="#FFD65D" points="119.452,122.709 120.359,167.968 75.737,160.1 74.83,114.841 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.334,178.35 52.325,177.884 74.854,160.399 74.863,160.865 "/> - </g> - <g> - <polygon fill="#7684CA" points="52.775,178.427 52.334,178.35 74.863,160.865 75.305,160.943 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.325,177.884 51.399,131.711 73.929,114.227 74.854,160.399 "/> - </g> - <g> - <polygon fill="#7A88CC" points="51.399,131.711 51.39,131.246 73.919,113.761 73.929,114.227 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.3,132.325 74.829,114.841 75.737,160.1 53.207,177.584 "/> - </g> - <g> - <polygon fill="#FFDD77" points="53.207,177.584 52.3,132.325 74.83,114.841 75.737,160.1 "/> - </g> - <g> - <polygon fill="#7684CA" points="51.39,131.246 73.919,113.761 74.361,113.839 51.831,131.323 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.298,186.454 52.775,178.427 75.305,160.943 120.828,168.97 "/> - </g> - <g> - <polygon fill="#FFDC72" points="97.829,185.452 53.207,177.584 75.737,160.1 120.359,167.968 "/> - </g> - <g> - <polygon fill="#7684CA" points="53.207,177.584 75.737,160.1 120.359,167.968 97.829,185.452 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.739,186.532 98.298,186.454 120.828,168.97 121.269,169.048 "/> - </g> - <g> - <polygon fill="#7A88CC" points="98.73,186.066 121.26,168.582 121.269,169.048 98.739,186.532 "/> - </g> - <g> - <polygon fill="#FFD65D" points="96.922,140.193 97.829,185.452 53.207,177.584 52.3,132.325 "/> - </g> - <g> - <polygon fill="#7684CA" points="96.922,140.193 52.3,132.325 74.829,114.841 119.452,122.709 "/> - </g> - <g> - <polygon fill="#FFDC72" points="52.3,132.325 74.83,114.841 119.452,122.709 96.922,140.193 "/> - </g> - <g> - <polygon fill="#7A88CC" points="97.829,185.452 96.922,140.193 119.452,122.709 120.359,167.968 "/> - </g> - <g> - <polygon fill="#FFDD77" points="96.922,140.193 119.452,122.709 120.359,167.968 97.829,185.452 "/> - </g> - <g> - <polygon fill="#7684CA" points="51.831,131.323 74.361,113.839 119.883,121.866 97.354,139.35 "/> - </g> - <g> - <polygon fill="#7A88CC" points="97.805,139.894 120.334,122.409 121.26,168.582 98.73,186.066 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.354,139.35 119.883,121.866 120.325,121.943 97.795,139.428 "/> - </g> - <g> - <polygon fill="#7A88CC" points="97.795,139.428 120.325,121.943 120.334,122.409 97.805,139.894 "/> - </g> - <g> - <path fill="#6272C3" d="M97.354,139.35l0.441,0.078l0.01,0.466l0.925,46.173l0.009,0.465l-0.441-0.078l-45.522-8.027 - l-0.441-0.078l-0.009-0.466l-0.926-46.173l-0.009-0.465l0.441,0.078L97.354,139.35z M97.829,185.452l-0.907-45.259 - L52.3,132.325l0.907,45.259L97.829,185.452"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M68.494,61.511l0.45,0.079l0.009,0.465l0.926,46.173l0.009,0.465l-0.45-0.079l-45.514-8.025 - l-0.45-0.08l-0.009-0.465l-0.925-46.173l-0.01-0.465l0.45,0.08L68.494,61.511z M68.978,107.614L68.07,62.356l-44.63-7.87 - l0.907,45.259L68.978,107.614"/> - </g> - <g> - <polygon fill="#FFD65D" points="68.07,62.356 68.978,107.614 24.347,99.745 23.44,54.486 "/> - </g> - <g> - <polygon fill="#7A88CC" points="0.944,117.995 0.935,117.529 23.464,100.045 23.474,100.51 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.394,118.074 0.944,117.995 23.474,100.51 23.924,100.59 "/> - </g> - <g> - <polygon fill="#7A88CC" points="0.935,117.529 0.009,71.356 22.539,53.872 23.464,100.045 "/> - </g> - <g> - <polygon fill="#7A88CC" points="0.009,71.356 0,70.891 22.529,53.406 22.539,53.872 "/> - </g> - <g> - <polygon fill="#FFDD77" points="1.817,117.229 0.91,71.971 23.44,54.486 24.347,99.745 "/> - </g> - <g> - <polygon fill="#7A88CC" points="0.91,71.971 23.44,54.486 24.347,99.745 1.817,117.229 "/> - </g> - <g> - <polygon fill="#7684CA" points="0,70.891 22.529,53.406 22.979,53.486 0.45,70.97 "/> - </g> - <g> - <polygon fill="#7684CA" points="46.908,126.099 1.394,118.074 23.924,100.59 69.438,108.615 "/> - </g> - <g> - <polygon fill="#FFDC72" points="46.448,125.099 1.817,117.229 24.347,99.745 68.978,107.614 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.817,117.229 24.347,99.745 68.978,107.614 46.448,125.099 "/> - </g> - <g> - <polygon fill="#7684CA" points="47.358,126.179 46.908,126.099 69.438,108.615 69.888,108.694 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.349,125.713 69.878,108.229 69.888,108.694 47.358,126.179 "/> - </g> - <g> - <polygon fill="#7A88CC" points="46.448,125.099 45.541,79.84 68.07,62.356 68.978,107.614 "/> - </g> - <g> - <polygon fill="#FFDC72" points="0.91,71.971 23.44,54.486 68.07,62.356 45.541,79.84 "/> - </g> - <g> - <polygon fill="#7684CA" points="45.541,79.84 0.91,71.971 23.44,54.486 68.07,62.356 "/> - </g> - <g> - <polygon fill="#FFD65D" points="45.541,79.84 46.448,125.099 1.817,117.229 0.91,71.971 "/> - </g> - <g> - <polygon fill="#FFDD77" points="45.541,79.84 68.07,62.356 68.978,107.614 46.448,125.099 "/> - </g> - <g> - <polygon fill="#7684CA" points="0.45,70.97 22.979,53.486 68.494,61.511 45.964,78.995 "/> - </g> - <g> - <polygon fill="#7A88CC" points="46.423,79.54 68.953,62.056 69.878,108.229 47.349,125.713 "/> - </g> - <g> - <polygon fill="#7684CA" points="45.964,78.995 68.494,61.511 68.943,61.59 46.414,79.075 "/> - </g> - <g> - <polygon fill="#7A88CC" points="46.414,79.075 68.943,61.59 68.953,62.056 46.423,79.54 "/> - </g> - <g> - <path fill="#6272C3" d="M45.964,78.995l0.45,0.08l0.009,0.465l0.925,46.173l0.009,0.465l-0.45-0.08l-45.514-8.025l-0.45-0.079 - l-0.009-0.465L0.009,71.356L0,70.891l0.45,0.079L45.964,78.995z M46.448,125.099L45.541,79.84L0.91,71.971l0.907,45.258 - L46.448,125.099"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="220.954,90.156 221.861,135.406 177.23,127.537 176.323,82.286 "/> - </g> - <g> - <polygon fill="#7A88CC" points="153.827,145.787 153.818,145.329 176.348,127.845 176.356,128.302 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.277,145.866 153.827,145.787 176.356,128.302 176.807,128.382 "/> - </g> - <g> - <path fill="#6272C3" d="M221.376,89.303l0.45,0.08l0.009,0.465l0.926,46.181l0.009,0.458l-0.45-0.08l-45.514-8.025l-0.45-0.08 - l-0.009-0.458l-0.926-46.181l-0.01-0.465l0.45,0.08L221.376,89.303z M221.861,135.406l-0.907-45.25l-44.631-7.87l0.907,45.25 - L221.861,135.406"/> - </g> - <g> - <polygon fill="#7A88CC" points="153.818,145.329 152.892,99.148 175.422,81.664 176.348,127.845 "/> - </g> - <g> - <polygon fill="#7A88CC" points="152.892,99.148 152.883,98.683 175.412,81.198 175.422,81.664 "/> - </g> - <g> - <polygon fill="#7A88CC" points="153.793,99.771 176.323,82.286 177.23,127.537 154.7,145.021 "/> - </g> - <g> - <polygon fill="#FFDD77" points="154.7,145.021 153.793,99.771 176.323,82.286 177.23,127.537 "/> - </g> - <g> - <polygon fill="#7684CA" points="152.883,98.683 175.412,81.198 175.862,81.278 153.333,98.762 "/> - </g> - <g> - <polygon fill="#7684CA" points="199.791,153.891 154.277,145.866 176.807,128.382 222.321,136.407 "/> - </g> - <g> - <polygon fill="#FFDC72" points="199.331,152.891 154.7,145.021 177.23,127.537 221.861,135.406 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.7,145.021 177.23,127.537 221.861,135.406 199.331,152.891 "/> - </g> - <g> - <polygon fill="#7684CA" points="200.241,153.971 199.791,153.891 222.321,136.407 222.771,136.486 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.232,153.513 222.761,136.029 222.771,136.486 200.241,153.971 "/> - </g> - <g> - <polygon fill="#FFD65D" points="198.424,107.64 199.331,152.891 154.7,145.021 153.793,99.771 "/> - </g> - <g> - <polygon fill="#7684CA" points="198.424,107.64 153.793,99.771 176.323,82.286 220.954,90.156 "/> - </g> - <g> - <polygon fill="#FFDC72" points="153.793,99.771 176.323,82.286 220.954,90.156 198.424,107.64 "/> - </g> - <g> - <polygon fill="#7A88CC" points="199.331,152.891 198.424,107.64 220.954,90.156 221.861,135.406 "/> - </g> - <g> - <polygon fill="#FFDD77" points="198.424,107.64 220.954,90.156 221.861,135.406 199.331,152.891 "/> - </g> - <g> - <polygon fill="#7684CA" points="153.333,98.762 175.862,81.278 221.376,89.303 198.847,106.787 "/> - </g> - <g> - <polygon fill="#7A88CC" points="199.306,107.332 221.835,89.848 222.761,136.029 200.232,153.513 "/> - </g> - <g> - <polygon fill="#7684CA" points="198.847,106.787 221.376,89.303 221.826,89.382 199.297,106.867 "/> - </g> - <g> - <polygon fill="#7A88CC" points="199.297,106.867 221.826,89.382 221.835,89.848 199.306,107.332 "/> - </g> - <g> - <path fill="#6272C3" d="M198.847,106.787l0.45,0.08l0.009,0.465l0.926,46.181l0.009,0.458l-0.45-0.08l-45.514-8.025 - l-0.45-0.08l-0.009-0.458l-0.926-46.181l-0.009-0.465l0.45,0.08L198.847,106.787z M199.331,152.891l-0.907-45.25l-44.631-7.87 - l0.907,45.25L199.331,152.891"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="169.834,81.065 170.741,126.316 126.119,118.448 125.211,73.197 "/> - </g> - <g> - <polygon fill="#7A88CC" points="102.716,136.697 102.707,136.24 125.236,118.756 125.245,119.213 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.158,136.775 102.716,136.697 125.245,119.213 125.687,119.291 "/> - </g> - <g> - <path fill="#6272C3" d="M170.265,80.222l0.442,0.078l0.009,0.457l0.926,46.182l0.009,0.457l-0.442-0.078l-45.522-8.027 - l-0.442-0.078l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.442,0.078L170.265,80.222z M170.741,126.316l-0.907-45.251 - l-44.623-7.868l0.907,45.251L170.741,126.316"/> - </g> - <g> - <polygon fill="#7A88CC" points="102.707,136.24 101.781,90.059 124.311,72.575 125.236,118.756 "/> - </g> - <g> - <polygon fill="#7A88CC" points="101.781,90.059 101.771,89.602 124.301,72.118 124.311,72.575 "/> - </g> - <g> - <polygon fill="#7A88CC" points="102.682,90.682 125.211,73.197 126.118,118.448 103.589,135.932 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="103.589,135.932 102.682,90.682 125.211,73.197 126.119,118.448 "/> - </g> - <g> - <polygon fill="#7684CA" points="101.771,89.602 124.301,72.118 124.743,72.195 102.213,89.68 "/> - </g> - <g> - <polygon fill="#7684CA" points="148.68,144.802 103.158,136.775 125.687,119.291 171.209,127.318 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.589,135.932 126.118,118.448 170.741,126.316 148.211,143.801 "/> - </g> - <g> - <polygon fill="#769AC7" points="148.211,143.8 103.589,135.932 126.119,118.448 170.741,126.316 "/> - </g> - <g> - <polygon fill="#7684CA" points="149.122,144.88 148.68,144.802 171.209,127.318 171.651,127.396 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.112,144.423 171.642,126.939 171.651,127.396 149.122,144.88 "/> - </g> - <g> - <polygon fill="#7684CA" points="147.304,98.55 102.682,90.682 125.211,73.197 169.834,81.065 "/> - </g> - <g> - <polygon fill="#7A88CC" points="148.211,143.801 147.304,98.55 169.834,81.065 170.741,126.316 "/> - </g> - <g> - <polygon fill="#769AC7" points="102.682,90.682 125.211,73.197 169.834,81.065 147.304,98.55 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="147.304,98.55 169.834,81.065 170.741,126.316 148.211,143.8 "/> - </g> - <g> - <path fill="#6272C3" d="M147.736,97.707l0.442,0.078l0.009,0.457l0.926,46.181l0.009,0.457l-0.442-0.078l-45.522-8.026 - l-0.442-0.078l-0.009-0.457l-0.926-46.181l-0.009-0.457l0.442,0.078L147.736,97.707z M148.211,143.801l-0.907-45.251 - l-44.623-7.868l0.907,45.25L148.211,143.801"/> - </g> - <g> - <polygon fill="#628CBE" points="147.304,98.55 148.211,143.8 103.589,135.932 102.682,90.682 "/> - </g> - <g> - <polygon fill="#7684CA" points="102.213,89.68 124.743,72.195 170.265,80.222 147.736,97.707 "/> - </g> - <g> - <polygon fill="#7A88CC" points="148.187,98.242 170.716,80.757 171.642,126.939 149.112,144.423 "/> - </g> - <g> - <polygon fill="#7684CA" points="147.736,97.707 170.265,80.222 170.707,80.3 148.178,97.785 "/> - </g> - <g> - <polygon fill="#7A88CC" points="148.178,97.785 170.707,80.3 170.716,80.757 148.187,98.242 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="118.452,71.715 119.359,116.966 74.737,109.098 73.83,63.847 "/> - </g> - <g> - <polygon fill="#7A88CC" points="51.326,127.346 51.317,126.889 73.847,109.404 73.855,109.862 "/> - </g> - <g> - <polygon fill="#7684CA" points="51.776,127.425 51.326,127.346 73.855,109.862 74.306,109.941 "/> - </g> - <g> - <path fill="#6272C3" d="M118.884,70.872l0.441,0.078l0.009,0.458l0.926,46.181l0.009,0.458l-0.441-0.078l-45.522-8.027 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.458l0.45,0.08L118.884,70.872z M119.359,116.966l-0.907-45.25L73.83,63.847 - l0.907,45.25L119.359,116.966"/> - </g> - <g> - <polygon fill="#7A88CC" points="51.317,126.889 50.391,80.708 72.921,63.223 73.847,109.404 "/> - </g> - <g> - <polygon fill="#7A88CC" points="50.391,80.708 50.382,80.25 72.912,62.766 72.921,63.223 "/> - </g> - <g> - <polygon fill="#7A88CC" points="51.3,81.332 73.83,63.847 74.737,109.098 52.208,126.582 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="52.208,126.582 51.3,81.332 73.83,63.847 74.737,109.098 "/> - </g> - <g> - <polygon fill="#7684CA" points="50.382,80.25 72.912,62.766 73.362,62.845 50.832,80.33 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.298,135.452 51.776,127.425 74.306,109.941 119.828,117.968 "/> - </g> - <g> - <polygon fill="#7684CA" points="52.208,126.582 74.737,109.098 119.359,116.966 96.83,134.45 "/> - </g> - <g> - <polygon fill="#769AC7" points="96.83,134.45 52.208,126.582 74.737,109.098 119.359,116.966 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.74,135.53 97.298,135.452 119.828,117.968 120.27,118.046 "/> - </g> - <g> - <polygon fill="#7A88CC" points="97.73,135.073 120.26,117.588 120.27,118.046 97.74,135.53 "/> - </g> - <g> - <polygon fill="#628CBE" points="95.923,89.2 96.83,134.45 52.208,126.582 51.3,81.332 "/> - </g> - <g> - <polygon fill="#7684CA" points="95.922,89.2 51.3,81.332 73.83,63.847 118.452,71.715 "/> - </g> - <g> - <polygon fill="#769AC7" points="51.3,81.332 73.83,63.847 118.452,71.715 95.923,89.2 "/> - </g> - <g> - <polygon fill="#7A88CC" points="96.83,134.45 95.922,89.2 118.452,71.715 119.359,116.966 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="95.923,89.2 118.452,71.715 119.359,116.966 96.83,134.45 "/> - </g> - <g> - <polygon fill="#7684CA" points="50.832,80.33 73.362,62.845 118.884,70.872 96.354,88.356 "/> - </g> - <g> - <polygon fill="#7A88CC" points="96.805,88.892 119.334,71.407 120.26,117.588 97.73,135.073 "/> - </g> - <g> - <polygon fill="#7684CA" points="96.354,88.356 118.884,70.872 119.325,70.95 96.796,88.434 "/> - </g> - <g> - <polygon fill="#7A88CC" points="96.796,88.434 119.325,70.95 119.334,71.407 96.805,88.892 "/> - </g> - <g> - <path fill="#6272C3" d="M96.354,88.356l0.441,0.078l0.009,0.458l0.926,46.181l0.009,0.458l-0.441-0.078l-45.522-8.027 - l-0.45-0.079l-0.009-0.458l-0.926-46.181l-0.009-0.458l0.45,0.08L96.354,88.356z M96.83,134.45L95.922,89.2L51.3,81.332 - l0.907,45.25L96.83,134.45"/> - </g> - </g> - </g> - </g> - <g id="Layer_2"> - <g> - <path fill="#0721A0" d="M308.919,231.15l-0.598-0.598v-3.055l0.598-0.531c2.699-0.043,4.857-0.199,6.475-0.465 - c1.615-0.266,2.656-0.707,3.121-1.328c0.465-0.619,0.773-1.615,0.93-2.988c0.154-1.371,0.309-4.471,0.465-9.297 - c0.154-4.824,0.232-8.277,0.232-10.359v-53.656c0-2.258-0.41-3.928-1.229-5.014c-0.82-1.084-1.938-1.77-3.354-2.059 - c-1.418-0.287-3.83-0.453-7.238-0.498l-0.531-0.531v-3.121l0.531-0.598c6.109,0.223,10.115,0.332,12.02,0.332 - c4.471,0,8.145-0.109,11.023-0.332l21.184,27.492l31.145,39.047c3.762,4.738,7.592,9.23,11.488,13.48v-51.398 - c0-1.594-0.078-4.99-0.232-10.193c-0.156-5.201-0.344-8.5-0.564-9.895c-0.223-1.395-0.609-2.334-1.162-2.822 - c-0.555-0.486-1.594-0.852-3.121-1.096c-1.527-0.242-3.598-0.387-6.209-0.432l-0.531-0.531v-3.055l0.531-0.598 - c3.586,0.223,8.699,0.332,15.34,0.332c6.064,0,10.469-0.109,13.215-0.332l0.598,0.598v3.055l-0.531,0.531 - c-2.922,0.045-5.18,0.211-6.773,0.498c-1.594,0.289-2.59,0.764-2.988,1.428s-0.676,1.639-0.83,2.922 - c-0.156,1.285-0.311,4.34-0.465,9.164c-0.156,4.826-0.232,8.301-0.232,10.426v31.543l0.066,18.262 - c0.088,5.977,0.176,11.977,0.266,17.996c-2.215-0.754-6.043-1.684-11.488-2.789l-4.715-5.578l-14.145-17.266l-45.023-56.246 - v50.934c0,2.037,0.076,5.668,0.232,10.891c0.154,5.225,0.342,8.391,0.564,9.496c0.221,1.107,0.508,1.871,0.863,2.291 - c0.354,0.422,1.117,0.809,2.291,1.162c1.172,0.355,3.596,0.555,7.271,0.598l0.531,0.531v3.121l-0.531,0.531 - c-2.303-0.311-6.021-0.465-11.156-0.465C319.653,230.686,313.876,230.84,308.919,231.15z"/> - <path fill="#0721A0" d="M440.536,167.4l1.262,0.863c-0.488,5.801-0.73,13.172-0.73,22.113v15.473 - c0,5.668,0.432,9.607,1.295,11.82c0.863,2.215,2.357,3.908,4.482,5.08c2.125,1.174,4.648,1.76,7.57,1.76 - c3.23,0,6.176-0.631,8.832-1.893s4.881-3.043,6.674-5.346c1.793-2.301,2.822-3.852,3.088-4.648s0.441-3.031,0.531-6.707 - l0.199-7.438v-7.172c0-1.77-0.09-4.184-0.266-7.238c-0.178-3.055-0.322-4.936-0.432-5.645c-0.111-0.707-0.455-1.229-1.029-1.561 - c-0.576-0.332-1.727-0.498-3.453-0.498l-5.578-0.066l-0.598-0.531v-2.855l0.531-0.531c8.455-1.018,15.582-2.678,21.383-4.98 - l1.262,0.863c-0.488,5.801-0.73,13.172-0.73,22.113v11.688c0,0.664,0.109,6.088,0.332,16.27c0.088,3.764,0.32,6.055,0.697,6.873 - c0.375,0.82,0.896,1.395,1.561,1.727s2.633,0.498,5.91,0.498h1.859l0.598,0.531v2.656l-0.531,0.598 - c-6.242-0.355-10.426-0.531-12.551-0.531c-2.701,0-5.822,0.154-9.363,0.465l-0.598-0.531c0.266-4.516,0.465-8.344,0.598-11.488 - c-2.789,2.17-5.977,5.07-9.563,8.699c-1.373,1.373-3.344,2.523-5.91,3.453c-2.568,0.93-5.49,1.395-8.766,1.395 - c-4.959,0-8.822-0.787-11.588-2.357c-2.768-1.57-4.727-3.74-5.877-6.508c-1.152-2.766-1.727-7.537-1.727-14.311l0.066-5.246 - v-12.949c0-1.77-0.09-4.184-0.266-7.238c-0.178-3.055-0.322-4.936-0.432-5.645c-0.111-0.707-0.455-1.229-1.029-1.561 - c-0.576-0.332-1.727-0.498-3.453-0.498l-5.578-0.066l-0.598-0.531v-2.855l0.531-0.531 - C427.608,171.363,434.735,169.703,440.536,167.4z"/> - <path fill="#0721A0" d="M524.009,167.4l1.262,0.863c-0.266,2.922-0.443,6.42-0.531,10.492l6.309-5.777 - c1.77-1.637,2.943-2.689,3.52-3.154c0.574-0.465,1.926-1.006,4.051-1.627c2.125-0.619,4.359-0.93,6.707-0.93 - c4.426,0,8.301,1.096,11.621,3.287s5.777,5.213,7.371,9.064c5.977-5.666,9.463-8.809,10.459-9.43 - c0.996-0.619,2.578-1.25,4.748-1.893c2.168-0.641,4.338-0.963,6.508-0.963c3.496,0,6.707,0.754,9.629,2.258 - c2.922,1.506,5.246,3.477,6.973,5.91c1.727,2.436,2.732,5.07,3.021,7.902c0.287,2.834,0.432,6.73,0.432,11.688v6.973 - c0,0.754,0.133,6.266,0.398,16.535c0.088,4.207,0.553,6.707,1.395,7.504c0.84,0.797,3.563,1.195,8.168,1.195l0.531,0.531v2.855 - l-0.531,0.531c-5.623-0.355-9.43-0.531-11.422-0.531c-1.107,0-4.316,0.176-9.629,0.531l-0.863-0.73 - c0.574-5.754,0.863-13.258,0.863-22.512v-7.969c0-7.039-0.332-11.963-0.996-14.775c-0.664-2.811-2.215-5.113-4.648-6.906 - c-2.436-1.793-5.336-2.689-8.699-2.689c-2.391,0-4.627,0.477-6.707,1.428c-2.082,0.953-3.951,2.369-5.611,4.25 - c-1.66,1.883-2.58,3.631-2.756,5.246c-0.178,1.617-0.266,4.594-0.266,8.932v8.301c0,1.949,0.088,5.602,0.266,10.957 - c0.176,5.357,0.365,8.479,0.564,9.363c0.199,0.887,0.508,1.518,0.93,1.893c0.42,0.377,0.906,0.621,1.461,0.73 - c0.553,0.111,2.932,0.299,7.139,0.564l0.598,0.531v2.789l-0.531,0.598c-5.535-0.355-10.869-0.531-16.004-0.531 - c-4.738,0-10.051,0.176-15.938,0.531l-0.598-0.598v-2.789l0.598-0.531c4.293-0.266,6.717-0.465,7.271-0.598 - c0.553-0.133,1.039-0.408,1.461-0.83c0.42-0.42,0.707-1.063,0.863-1.926c0.154-0.863,0.332-3.719,0.531-8.566 - s0.299-8.51,0.299-10.99v-7.57c0-5.002-0.344-8.854-1.029-11.555c-0.688-2.699-2.158-4.98-4.416-6.84s-5.092-2.789-8.5-2.789 - c-2.656,0-5.08,0.521-7.271,1.561c-2.191,1.041-3.984,2.324-5.379,3.852s-2.258,2.955-2.59,4.283s-0.498,4.207-0.498,8.633 - v9.828c0,1.949,0.088,5.602,0.266,10.957c0.176,5.357,0.365,8.479,0.564,9.363c0.199,0.887,0.508,1.518,0.93,1.893 - c0.42,0.377,0.906,0.621,1.461,0.73c0.553,0.111,2.932,0.299,7.139,0.564l0.598,0.531v2.789l-0.531,0.598 - c-5.535-0.355-10.869-0.531-16.004-0.531c-5.092,0-10.404,0.176-15.938,0.531l-0.598-0.598v-2.789l0.598-0.531 - c4.293-0.266,6.717-0.465,7.271-0.598c0.553-0.133,1.039-0.408,1.461-0.83c0.42-0.42,0.707-1.063,0.863-1.926 - c0.154-0.863,0.332-3.719,0.531-8.566s0.299-8.51,0.299-10.99v-13.082c0-1.77-0.09-4.184-0.266-7.238 - c-0.178-3.055-0.322-4.936-0.432-5.645c-0.111-0.707-0.455-1.229-1.029-1.561c-0.576-0.332-1.727-0.498-3.453-0.498 - l-5.578-0.066l-0.598-0.531v-2.855l0.531-0.531C511.081,171.363,518.208,169.703,524.009,167.4z"/> - <path fill="#0721A0" d="M623.552,136.854c11.156,0.355,18.262,0.531,21.316,0.531c4.16,0,8.832-0.109,14.012-0.332 - c5.488-0.176,8.875-0.266,10.16-0.266c6.197,0,11.322,0.643,15.373,1.926c4.051,1.285,7.404,3.631,10.061,7.039 - c2.656,3.41,3.984,7.527,3.984,12.352c0,3.984-0.93,7.836-2.789,11.555s-4.449,6.885-7.77,9.496 - c-3.32,2.613-6.686,4.482-10.094,5.611c-3.41,1.129-6.951,1.693-10.625,1.693c-2.746,0-5.801-0.309-9.164-0.93l-1.129-4.051 - l0.598-0.664c3.32,0.754,6.02,1.129,8.102,1.129c5.799,0,10.459-1.914,13.979-5.744c3.52-3.828,5.279-8.732,5.279-14.709 - c0-6.02-1.826-10.824-5.479-14.41s-9.176-5.379-16.568-5.379c-4.029,0-8.457,0.754-13.281,2.258 - c-0.398,3.41-0.598,11.645-0.598,24.703v30.746l0.066,12.949c0.043,5.004,0.176,8.234,0.398,9.695 - c0.221,1.461,0.541,2.436,0.963,2.922c0.42,0.488,1.229,0.896,2.424,1.229s4.139,0.588,8.832,0.764l0.531,0.398v3.32 - l-0.531,0.531c-11.289-0.355-17.643-0.531-19.059-0.531c-1.506,0-7.836,0.176-18.992,0.531l-0.531-0.531v-3.32l0.531-0.398 - c4.117-0.176,6.861-0.398,8.234-0.664c1.371-0.266,2.291-0.607,2.756-1.029c0.465-0.42,0.84-1.316,1.129-2.689 - c0.287-1.371,0.453-4.426,0.498-9.164l0.066-14.012v-30.746l-0.133-12.949c-0.045-5.047-0.166-8.301-0.365-9.762 - s-0.51-2.434-0.93-2.922c-0.422-0.486-1.229-0.885-2.424-1.195c-1.195-0.309-4.141-0.553-8.832-0.73l-0.531-0.465v-3.32 - L623.552,136.854z"/> - <path fill="#0721A0" d="M704.767,266.744c1.504-3.719,2.633-7.129,3.387-10.227h1.594c2.168,2.346,4.516,3.52,7.039,3.52 - c3.143,0,5.877-1.561,8.201-4.682s5.611-10.018,9.861-20.686c-1.684-4.736-3.387-9.207-5.113-13.414l-10.559-26.164 - c-1.107-2.699-2.746-6.563-4.914-11.588c-2.17-5.023-3.465-7.791-3.885-8.301c-0.422-0.508-0.963-0.984-1.627-1.428 - c-0.664-0.441-2.457-0.863-5.379-1.262l-0.531-0.531v-2.656l0.598-0.531c5.09,0.311,10.314,0.465,15.672,0.465 - c6.285,0,11.133-0.154,14.543-0.465l0.531,0.531v2.656l-0.465,0.531c-0.488,0.045-1.85,0.145-4.084,0.299 - c-2.236,0.156-3.631,0.51-4.184,1.063c-0.555,0.555-0.83,1.186-0.83,1.893c0,0.754,0.686,3.199,2.059,7.338 - c1.371,4.141,2.699,7.803,3.984,10.99l4.184,10.359c2.744,6.818,4.869,11.844,6.375,15.074l3.52-7.77 - c1.238-2.832,3.01-7.127,5.313-12.883l5.512-14.078c1.416-3.629,2.225-5.887,2.424-6.773c0.199-0.885,0.299-1.527,0.299-1.926 - c0-0.973-0.355-1.637-1.063-1.992c-0.709-0.354-2.281-0.664-4.715-0.93l-2.789-0.398l-0.465-0.531v-2.922l0.531-0.531 - c3.719,0.311,8.123,0.465,13.215,0.465c4.648,0,8.367-0.154,11.156-0.465l0.531,0.531v2.922l-0.531,0.531 - c-1.639,0.045-2.945,0.266-3.918,0.664c-0.975,0.398-1.904,1.107-2.789,2.125c-0.887,1.02-2.48,3.885-4.781,8.6 - c-2.303,4.715-4.605,9.596-6.906,14.643l-6.508,14.277l-15.34,35.859c-1.328,3.098-2.979,6.418-4.947,9.961 - c-1.971,3.541-4.383,6.098-7.238,7.67c-2.855,1.57-5.877,2.357-9.064,2.357C709.88,268.936,707.245,268.205,704.767,266.744z"/> - </g> - </g> - </g> -</switch> -<i:pgf id="adobe_illustrator_pgf"> - <![CDATA[ - eJzsveuOJMeRJvq/gX6HPD8GEHFW1XG/CAcLRGRGarWQREKiZmeOsCCK7CLZO91V3OrumeV5+mN3 -N/fwyKyqLHEoqtNZxerITI/wm7m52Wef/dP/9cWffz29vvv65tf1VbF7+eKf/ml/f3P94e7+Nzu6 -vPvd27cf33+4x0u/+tNnu7K6KvBT0++Gr+ST/3xz//7N3e1v6L2rEt894vd/9eX99b+/eb/75+uP -333/4bPdr5bbD9/f4d//Zfe722+uPsNPfvnmw9sb+Oztx3c//Pj27ru7q+s3n9ljQLWH6w/wfvuq -6l5VRTHs+t+03e6LP+BH5ruPt6/f3H433/2f3+yGXTlWu37od21BT/jf3vzp5n38katxbMoGP3hV -dG0Dn+6umgK+1ozjVVW2A37vcPfNx3c3tx++uL/75ub9+/3d27v797/Z7X+8vt394fo7eOd69683 -b9/e/cdufnv9zb/57xzvbj/AZ7+4fgvPfnv3e/j58OMPN7/+092761v/wT/e3Ly+eX3u49Pv2q+O -b97eQHe+u/4Az1/QxbL6av745u3rP3589/UNdHRVtHS9/ooe9i/v4SnhgfFvut5/9bt3cOnPNx/g -Lt/BDal//vTbeQ8je/eOPggXqfzqr3+6+e4NDTh0/v/8TD7pO4M+etVVfdOW8Effd01f7n7127d3 -X1+/3f3h5vWbDzf399e3N9Bh89uPN1TH/021h4998fH+Jn63xLv7d397f3NzK2+X/HD+7T/dvA5v -XjXjUNSj+8yf//fH6/ff2yd87Tx6n0mffXnz7gfo/xuaIk1RXLW7CqZD6//Wj8JY0MfKbtfB7Gl2 -7djIe2Ha3Pz7m5v/+M3uj3e3NzKG0/2HP7/5/2BMhqLYdUUhl//08e3N/V9u3+AcqOjayCP4h7vX -N2/hLuHrx7fXNHDSi/ZbPvHl9f13Nx9gvt+9/fiB1uJgd4FZ8vvrH2/u3U0+/+Hm9su7f6bH/HUF -C6EbauyjYix2A7StHOgOsFJKvRf3oD4RVoBf14r7Hvv5C5hkn9+/+e7N7W/00fqvfnv/5nWYeX0F -C5V+UcVXg/sZ9UeeEtr84cPNrfYDzPr9H9wcLq7+8Ge863L7en/3Drv+PQkFmL63MF1Blsi74R/0 -HlTx8YeXL/768kU9vvrfH+8+3LyHGt/e7Mbu1XcgsW5Ajg2vlo/3d7uyLtwnvr5+f/PqWxieN7d8 -9fXXfA2m05sf3r+B2796ff3ddzf38j94/9U3b+5hXnz79ub/vPrh5h7l3/vr29ev/vzN9f3d7avv -YBlT3W9vvv3w6vNlVzYt140XoD76G7r0+w92S/qo/oPf+vojXP3w6ub2NUz5Vzfv6H8fQELcvIKF -/Prm3fU9iKlX75Ob8pfvbl69vgMh/P79m13Zjq/+9fWbm3to8fvdq/c/XH8D3dE1r775eH9/c/vN -j/CP7tXX93f/dnP79TVIl7IbXunnX31z98OPUuf9629v3r25fXMLX++rV9D/b765fgvi7dX3P/7w -/c3tq3uSMvDF16/eXX+DjwXdCoP06gfYbuCbH9+/+vAfd+8/Qqe9ubt/9eF7EAb2r+tvPn64efXu -I0zU+hVde/0NjD/V9g3In7dvr6EmWH/2DXigd9fvv/n4lp5oGPBNEBD38B388/vrt9/yPeTie9wk -Xk00H6Cqie84udGcuH8na/2EDXg1La/28givFvr6q4W+DLUs7uuLfe93/Knf8S1+5z7zO/vM8uH7 -V3+kG0I1n/MXPucvfO6+8Dk/0+f2vXcf335488PbH199/v4tzom/aIP+wl/+i/vyX+xb/8pvfvn9 -3T3MlhvYe25hqr1/dc03vtb2XLtvX/Otr62Sa+qO65tX32h33PDXb7j2m/BlqOrGvveGP/WGP/XG -3eKNfeYGuuOWb3jHH7/TZ7pzX7iTj9j3Xr/59zd4gTvjI3/1I9/pY/Q8H+07P/LbH6gzftTLL198 -ubAsbP/7V1++B8nvBH79FQma5fabO9Q9frP7Kr/Db278f3218Y1XGxXxvvDl//uS7g4yT++9+/L+ -482X8EER3PRgX8GTfgHD84FW3B9/oPeGr754+xHe/O393ccffnf77d3LF79iDfCL6w/fw1YN0uU9 -KHF8jf+546/A1d+/+Xe9CArcD5+dqRI0w2/w6T7/+n/dfIOqoVwIf/35I2gRD6nqC9wk7m8/v+XH -vP/4/vvdl3d3b+1R5QPylj0xbKr8nZ/NTezDuRvAmz/fyvfXsAPCavrh+zff5OrPvG832vjuQ24L -myj2eu6O8Vt2s/U3fh73wY7+9s3ta/gKTfzQc3fvfsAjzO7P31//gJfxk0f3yYc8/+HmWzhAuLGl -q8vtv9+8vfvhJly3KyDvd//j+v6HB3XOj+++vnv75v270Cfuiv39kKpA4bt3j0P/hP9/i/9/0Cp9 -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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - SuahvfO06AgoJLtb+hUHZmdeD5pr0i9hnuksC9nzdIaFDHlubomvKV3ZOrO8b8nDy+NZFc0paGvc -psq3h8PFTfsI65bXrK1YWauag2rspmSN8vzi1alrs5QMdGFVzvBIuhKDF0gyjsn649mhMo97xtYc -HfRzfYI9Iv1he4fLeRodMnVHWec99Sw+Ma85HTsjXvNGrNYxv3nIzRBYqpZVqFCAsqeMP2MKZH8Q -r0+6y29x3Ti2G0O8zJZJ9pzHZ314116lHoUePwgrtzJ6cS9SmBXnOnMoPrWnry3qqU19H3lwWgF2 -R1b1DOvtQ3hv15q/na3Ia2U6qvPbLEH3N3/MI9hFtoxtvhcor1vaCxd5FUBonWX8NR9LAv5Z+Vke -6WmB/kn75Em9EvoEgxNWjMhJz6xZEyySuFPsREtgDoOYhADkq0rjvAV7kQFDPEt1AtBQDEctNSgK -BitTZEyXICoGCpL2XAcXVcNPUjV90dJXG0RxUBsY6uGhJr3GjhvmpMe/ocouQfg8X41p9LnG3+sf -PmZcqR3gD3wXG1pegThssMG5+PMnV8ZPNSpWiTofRrrqPWynhK2hoNFXgoUaSR8E09RfgYRu3GM9 -R20yr2QeGJtHiSH+lcB1pH0BrsMDRNXAd7ty8HPr0qouBv9UefRP9RnRdwT4Dwk8coFRyofCHJW8 -7epmO7pURJIMwmDPkYotUUY+FYxCnPcWratgjsqiwlJo86xxYEgpaXB1hnOwss0vhZ6jqs0xuwoy -13hdBZQzmJwSf0mSH43IbSmxZ28pUDXt8iLBQAoPbx04vLfkyKReSxS5Ji0OCcftaC5JTG1rTwg6 -9RioyuQoSTcCCJn7VEFIC0WcqSrZmyLJgCM8krASyQlvFdrFCW0n6gdOUlubcj0TNdhCqaUrUh9R -pQ7KY0mJTFF1FBpVUqJRheajAR/J9EBGivPLF6DNKc+SxGU6hiXPr3TQtNynX9scBea4rcQLoT8M -OmZrziKg5PXPwX4OAmLek6I3keI3uZ9R4sdGI8GOf1rhVlDKLqXt0h+mvi5WPxe8VupuKUpuiKzz -ai0+JwOYcHofCKrN8GxVYuHgIJ6g0Zzc5GCuThG8Pm8b/r5rPH/A0EjGAClzhJqFBfBseru9v3uw -hOCdoa4VcR0xRhC98jKmyRs1cm0wT3gn6Qkb84hXLhLwOIQEjBzRtpe0h5rycJCQXE572CZxgRTr -JuksQqIKTUwxScSvRghqJG+ciCLE4+aSUCiQjWNmOWZ5sKjY1o5FBvh4LoiUqu3QKxeq7anKLsk0 -ngEkpYcZgkflUkLEB7jzLjJz2JD3eoN7RhAAMXPLwbD6cQKbNH3NZgIbwqL4eAfFlQwnEQ7bSJK9 -pClPV1cc05BhY3GRDMnKEgjikEbaSoytW1vRyjpsryshcvcrS9bV+iDXM1MVnbBELS305DPgOQcv -6LGjdVr7CFMgOcVdXher2jW/BVp5JWRWhRDNoV6sB41e2d4GPhDyObC/ggOO5+Z6hsouVre7vLrd -Rep24f4r9T8/VimVWXKIzr+dI0sb+t1w1VT95nftA5e2PN/wLj1m2KZY0fanSGrFUXPs2CIc5GpN -y9vSlL09C4bJqCd++/Wc6L3bdoNlb6/88saITook0XQGsEyvUJlNdah0yp36FlOCA1XL9Ef/akUx -SOkNQOL7wOnjBptabXxqREssnGqDsaptwGlANp8k9pT7hTuGe4a7hvuGO48GWQlwHngCuF+WHDRi -I1vsnkzLHBMzr6mZGwsQt/BvDfSG00gI69YA7nEZ1xKzVILPQo/qKEOKIbD9KbvnyEFHtBbb/mro -2sqHJF1UT5YEsdidlAeXRw11fX5B95/RPVYrujqxog+rFZ3ax511nNAgTyJUsqPfLJC1+Pdkv6fM -bybJGuWwMwh1gaB9LKN8+N3J79797iRmtdffjz4k5bn3vZQgSQE11iQfWunxJiK8ypOeEBbCy43H -S401DM+B8GAdPzFe1mBzgSihM/hcIEbwZAgRCYKSHwTaA4ZzEl/kbOA6liweYNcbBU8rVMKCOTXZ -syaayJJKbJBJrEgkhELiPH3EijpiJZ3IJMwKD0ddKknuqFGGIytpa2nxuC+KiVvUuwINq6TeiR0V -VSoxal4NqngFo29q/7+snoul2piXaiNKtSgacvCHNHdECwc0JgLFw1mMVTQqUH8oe/li41iGZ5q8 -hykcyLLHMfIbPvhAtnUcczgpmGKn+PQTbyA/c8CyVYa01KOkPnucYZApKiljY4QJ1JZoa0KL0riu -KLbLyerOHTpDSx1KzUx73OpZSFCVCLV3MlRwYdAjARPmzD/iKWf6dIFBG6l6acg6oVM0+kOlXVRA -ddiPBjk0j0b6rvtKZ8i7mBJREXiCfTP829EbL+H52Xzp8/kF82VvyDjf/s72lTVlYsjXV6x6hHeV -54JzKeyF4jeeBc61RTPycF7BPKvgEZ7xVHaChEZFWxaAakMCmNL2hRa2PoDaSGhiSpVqBXOJoTxH -a/MKQgX7pLZ9nSPBw164D7QXtB+0J0K2BBAl0hvaH1GPeAqdyEui7QiwJAUmaXt8SLi1iqxJpbVN -A8PXoeEKVtqTjkDgHmtraC0B+KzNPe3Bo7U8tH3B3V7aX3LMGvUB90LHEcjUD9AT0CMTnVv21h+Z -PBKbx2NvE14SVTpVpvmIvD4ep8r0RpwInDY9TFHBoJJyCzayPNgphYGSzy9RgNfJ9LpM+/xxIT4w -WCspjC4XE3Pq0LAFfw1J2+IWJ6nyPPj1Se1aXGq12iWpcwnVHMnR5e0iwBWBsj3g9CS77C/Aq5M9 -QiW2i8edenLnHrFUgOR5FqYgS7FeMWv4Q70OF7XrRFAVtCvfqse1yXlRYB9/hB/lF90uJ+83YfI5 -yPYqgDuiC6mFRE+1qViXCtpGrEVVgmeINSjVKWAPpqhT1Z9yUHjcX3F3xZ2V9lTaT/GMrppTT7sn -75xsfzPtySC0Ct+MQt8RQOsIQio6WnkAt6fQ2wspC/rKjszD68hCPOMwHuEmOszhwx8RWyKokkrI -DtG3MgqqBJWPhf145J2pCT/RE0h5pFFF1YSJcSrq6YaQFAPNCFQ6jkYuiJ4n9EINQia4J0zFYT66 -fghspSHJu5JqBFqIimCxGoS0GEmbEm0wzVsIOwqkG0oaEag3lDriIKzUewvcGY2Iw4cd+cwChZFy -aII3Dufh8C6l5gjHuMrY4Rc6EDETvHHA80HiqigQC/jTgUQ3wJB0hH8WkOjjvKqUklaO8RF5ifcc -rjzysTc+4y+MPIUEMvce+JX3PfG7xz73tb99T1Q9syM9Sf3stRHIlMIRvQQaGQnTGx2VDBzow9x3 -/fCsr0fXOJwrUOPZzzyu+BrTHeSEv2UQS6ruh3431JDmxvLocCjrICwDuP/h7newXDod7Xq458XZ -dGoJXz3QOjWKS3LSKO+Asjz0Uk+jdZHnnT3uyiegXA1+9zxIaCzKgV785jzzddbvLUDWM1ErfS3M -dHMODEbDxDSivWjWTB/KOnUn1Ev7CKenJ4PoXEC8+koWurgzkKL0cHc/ChETB3tMtPPwjh6i59W8 -H7smBws32CeOFDxD6OkvJBNXZ0ohgRyNnR96y2bgUjGHHAaSwaCSoFKXK+HRrAO5k4idJ4V45cl5 -Lhxpm2Y34LOZkul0rdL7Dy6jjGY7mCwoeG8IVgkYEWdDTOUbE/lWwoivPPnKn9+F54o4RHSviuH7 -YZ56HhHdt0rDQ5EsXhF2xXRTuovFIR+OciqVF7KPxhwneRIsz3FSZnZUeiJ4vhxaSXZV7ZVcy6yW -7UCWdSiL7NGw1+QQUGJut7vGferpuya7oz673TNtQ9ANjG2e7pmY+O2utH6YJ531oVH2dd3TTxrm -I9P8bOlfQwizBi+LYdZCHB8YeLt5pkistKTvbtppH3TXJ+SdkdxLm7ln5K4PDjP2d92+7wPuGuEC -LiD0fCZe0DzaB96oTsICKvXmWULGWtIXhj9Y9/Y3pmgdrqDxz39BJRIAoh/XdKOFNtq+b3y2GpzT -an5P5GtNU0A+W40XpDGl4KBLk5dyJZeNVex3vaSWS72ueShJv2Kg3UuIQj4a2DtRYgfKmMS9inMI -Hto7T9i8kWOjj+OgBzIE+SRUZvQgmqlg+miTOPTgQDqIg+EojiR1MCj3LOg9Lno4xLYqA20cQezN -6p2we4aYdRV8IvpoQ+4T8UciUMSfikA2dHjxG7voLFad2GcbJxJjh50YmnBkkkwBPl+ARK+b7/Q0 -32wfOSUOjqWiTmKGA1/9Ivx9IVrYRwiryneUQJqQlSHw+obtRHIwZPlm1YikAUp75ZtNuADUkOR4 -/zet2VshAv4wEMIDzKkANR6yjoWYcXYxFd4r8CFjgjDO4qFHOCv42JMwzlJgmGecPawZZymoqyYD -ZmCcVQXPA9u3Y5W9ESaGt8dYitgc42HuMdA9zzUbTFzEk5pELMfGGW+eiVAWEc7CoO9yEFgTClaJ -4SqvaleJ+UoUZWGbjYMDtuO9t9hm54htttvsw22+3iLXh3bsf0aWWewx6MeHmfoeHEBBISB/I5bZ -DJus55T1xZOs1lZinlllmVWe2cHxzCrD7EMSADquBFo7QRqmHLOeIaW3nWY2I4ZnrWAVe6ID6l6D -NHM8s0Pglth6aXLRUPSV46K1vwmD0xr7rDLRsum5F/5ZNUgH/tnJWGe1KOssSP3IHMJm7kZS6mpS -xyFK6jhvcM42PJ7kWO5Ejirn7CzZVTjctjjbPz7NatxXuU/Y9ajG9DuN+1dtfVxbdpTapcXUv1ph -C45GYXWlEY5gZhBRHpFOfvfmGqBRIl2gF8SX/63MZfD7+RhnU85ZZZzdyoZSp1yom5yzFiNEaad8 -/B3HBxUi6Dzn7Gji7iQlOajGo7PgZz0cRkk4nyjnuWhXDLRQ42OzoxQ+LmudhwZ63Mdm9ZuxWSEy -K+7tiN9XEn2d6muKwLK4s5QhdqsUp0rgkYX+yXHKpsyyMbvsfmNUaAwkQ82aB/jRY6Bss7ZqUh5g -lxPomdlnvRU4BucHftjAPRsyYvbGDDsIJH9OGGgXzjEYyKpdvHfKNtuZhbxzPLPGMGtoKamXI5s5 -tOinYJ/13LDKPetDYTz7bK9csEJ9XMo0VP5Zoiy2es+xzuY4ZyPGWbtPdSjTqbEZSqQ1KwMt1qNP -6hlokz6RBEoHmY5ef0y1x5zumM9PEGvaj2KgtfSaDtUNWiPpik5TjPXERzPQ0pmjNVfww8Nr1wy0 -3KO99CmDVthtp73ZoDiVnlzE2dY7/tlKBCZoauzGo37sZEvSfmQLAfekbkMFnnsz6PhOe9NYh/ak -R86Ro70irXPtal+x0PpMIpS3b8VF5Xo453DPOd2tvyljAfd4lfS5nu3W5noz2WdYaPcRC+1e5gs7 -uCdxR+HvXk6yCvXg+SRstOT+Zv29MR7aAACphJv3KGy0pEs6IMjBGGknPbdQfh7WQg== - ]]> - <![CDATA[ - B8dE21nOQcbxOzZaiUNYBByibLSj4PrhGa2PG8nWUNJGoIy0a2z/6NhoG5nlTWCjZZejIP0PhpTX -KK3J4q4CI20raHhKt3yGiXYUJlp20sDTUhQCp7PdG3SlN6ZciT2AGVKsuGh1PvBT6HwoHRdt5MiJ -2Wgp2iLEG+TZaKvHsNH+3GCbztb3eIfxCUbaiHHxWRhpo7ydz8JICyppnCft4hAGkPWxVfpCRtps -P+dc87metr7G3LcuP826v3M9nnPPWZ/DYSf0eq7fcz2fCx+xvqcDWRpk4fs/NwK5MTAgJB2z0rAK -Pw4neWnPstLGjL4HlwtYLdHq4ox4aSXfT8xLq3M47s9JcjSv8wBHzLQEF9AMzMZMmw1kyYWyHCJ/ -DPW1+GQ0s/IoPa6zfrFQEA0DiXMCTmt2WjnSaeBH8MgMFvKxT7wxOhI6Fp6ZNmTjC7y09WqN+1m2 -htYeiDLM+6BCewMHa7UVoLRa261f25Sm1vPShgAlC1EKMXsiKb1vaU5yGvv1GtaqhyHPSikG/VM6 -blruqWPSRzpXUt5e7R1deYGl2HPT8shrn8TMtP7YqfvJXq13tqv4fUV3lpiTVvYY6J/ASds6Pagz -a+soPP5JLq+VnTUw08actKNBjp7KTFslHqHUJ2ReISEoc0CxM36hcKzfZqaNMhIKM20EizMfj4fW -rG3wa7BtBm7r7fCWVTD188Q5BTc8FCkrrTtv5eC37hzwi2SmzQUvPMgzk/fNiD94Iz3WyT7ZZKY9 -PztyzLQKkWkF7WGIGuOSNfxHJYwbJ6lpn6M+5ablGgLxpyRF9pV1iv/Q9Mgb/LQXVhUH8IdU0YHm -1hHKClhl4OadpKh9ngpThtoOY/9j7iYjlU3/OMtQ++TKlKGWIUIblLKa5/qhBLUXV3ZxYuo1Qe3F -dV0KDhrzzFFweVfuWgcPEiKoSXgoj5S+sLboyNlS6FYBzsCbVYRAtihIhIy46M6A4kbYTXlSOQrJ -niPqfmKoLS2KSFWkmY8upigxiX+fPbTUUVz4RBx0zEJ7FEZWf2BUhZJUZ6c4h+OKT6MtSbSJrZVj -pRtTL1lhDhHShySWi2OjGSKNcOMhREVTOrJZXB6Li4tWnpRK4uQb4nrtLDbaRUdTbLTFR798cRRK -F46SpijAi17bfLGfavz51RjFRChXLSq3OMVxYlXk5/HywEsDjuggt7648cVtT+zLXn2tnEQIBo96 -w6iUSgQ4nBtIrHKGjTGK8OCowlZYmPeOh7k0JmblYj4gL6scrmuJJOwF3khSQHioG4va5AM0xxEu -wqFQ0wGqIQkwiD8RP8KMCQt5MJUpQVd/WP/4MSY4KIi/uRTeJV77FM0JK175ECa37oX/yK36Sla9 -BlXwqheSTFr1WHjFoyVFWd7wtdjaf7Z5ddnrU41/uxrNhaKc88HZUecyUMuxW98Nn63lSF7ROq7E -VMxF3CVydNckfprCT2Nog+NkJrkwCuSKnSYcv9SL66QxB5SPqi30IGZp2fU4GtxTGl1L0DkxVfQC -LeqkAxpxXDUCQ6oNnCgQRU79JxG4h5BkBQ7umlR7L4l7FMY42f+n6F+jOD5C7C5Db7hwSm+G8IR3 -Q9JvnwLcQ7F6+ctfHwz2o+/o/wd37/gvjVvLF2kd1DhzHN+ZckwL9WamwMiUdXWynH81Ei9NL0ra -+LRXk798QY0br5+mxv8kppG1My1HtxiMbUy26LGca5LFlGgxlGFVyH8hSaRGgXKHv5Sea6uM9v4Y -fVKNf48p6V39v6boGdffytUnSTI2rk+UQWHrO3FR8NJpCBZx42vWgseWrUlA+CTNjZ17nXpPX6yd -SoEaM+jfB5Y2V+gk/Kzlp6nR4El7Ag8pdEiBQwhGQhASQ5zipNW9xaSXxsjCJs1ubsnF4qFC3piZ -A5mfNeuSG6g3toTUjBkbd3MhDWrKNGOmcPb7NGze5O3DG5QDoY/YP2oDfpScgFgIGRXskfB+OM4P -Bclo2j1l/lAgv/J/IPRZ2T9YQ2G3ieopzATCXCDMBtJFmorf52R3pP2zVF1FsA1h3w07cgBi713x -e7zoK5TaNugroQxJ6TOlyxWosdtSMA3UfL44lYDYO55WNl5ngOFPeP00NdqKPxIrBa96SVhPUERl -otBU9QxoFCYKAbQxbNKvedAdV/DAsOY9ODAfVnJ6xWsWhY149xNR6rOPUpcI9cB78pDVHph+orXO -Th9Y8adXehut9PU6X4znRzh+CA4Sr/KwwvvMCo/Xd7K2cU2jy9Ct7Ny6Xq3pZDUnq1jOCbkV/JC1 -mlllsj5PvcoHFVMWERz21FIecwVBcM9bfpoazeW4zsFyFuefRFb42Aq0g4cIizTLEWP/00xHpSis -ISMLM8lw9AUDXTU3S5z5SHmWOC4j5EDiIFjN1lK7SUtYQA5Vc5mRjsbIFDK5aDYX5asJZbJiDyFx -H+HVu9JFpU1KE5VaC4GrQv6luJSZkjnCWMYmKpSUNi7LyXI4Vyi59qHfP6HM+UIhapeWyRfJTPWM -5e+1xhUf1bkX8zkimyNyOSKTo3JXYVgBBxWIjoAWaII7cUiB8lUhBtfrCZ3oCXo6QNnDcAeWNAp9 -lxACggOqnhBCCFgCsI6gp4IY1hC4cAoXPgBaAfRLL4D3QAq/hngowOPg9n4FeAi8QwEe0KUMb8mk -XI7yopx6HcinhpyZyJhZHctED2NOMNXCOO9LOHtx7+LJq3E9O0m/ouQuTPNSvQskL4cRMFOXAEWg -L0EOahiGBmGEEAzUrVJK/YPpUth3Co5BuKn2HvdcSqbvQy8CICbHwScQINi7N0MvDDTLpPWo4xwI -KD4JRLw1baGoSoKtM2g9UL1nf4jlN/eOxiL5n8VI7yk+yaKfCv9DbF/2r+h9paNXevqtH6Wx94Ge -7cZPE306nF5yJxqvd23e36K42iQ9AYPnR8uyGYpa3lpHWt8mcHq23QUS+/psyShocTEC/NPl+SyW -T3idqtGFGodIvnwsXw6kn4fpN01LoeopVD/P7pYD62fg+nCCmjcg+w/nd9ritMpD9/NYjAx4X0Gs -oOnGAH4H4X9mBj0D6gun6wUcemlABMLvHsAV9oiACMdDfArh8oiACAwS3Mrq4IhGmJBlr1mjHS2L -guGVmoWzR/c2SxQWr8B4ni+M2ykMIK8QeQXJM5JnsCAEzi69N7C8wuUFMA+6h5LjhLmlwPmRIptn -QfwcNBeEQegVRK8hC5SFmgDUigXiTNTzg/MrZDMsSAx5yLGgQQ7FOhQ0IbgIdBaNA1V7OPW8BlNb -iAmzaSpZxWC5BcIcLpJwid4FSsTA9U6RGIGeIprBjpTHTv3BBhfoJpTiIHgqA7XEJED0vTFq4mo+ -kEaxqG/WUUk0RiHRkqeXV/koIXWHKCyHCSQYsdbaiuc176Trqg0JAYT8q8q0sXL7swa62f/NRqJW -lLDrBzKIOnlXiR9a95fRQAh8v41+I+DcpzJMyBxCoPq0MiV0gmVWM0JKFkDGA2c2wGXWOZqAGN0c -GwlzroE1FUPiGlgRMKQECz6cPwTyn0xqi70gNOFrc0psShFEtxlRDoLrnlc9gCl11lQJB59O4BQV -gqM7OGbJDtYkByhSYqqJlNwgR2xwklpCYsYzcyUT0dMP57iCgjE3TwfukPkgok5j80/h8rNU4OQm -OZdieTOnVyY/10SCwsdFcFQvQ1I4OkCjeHsXv4vHnEKj0aODkT5RmpNLIqWN1BR/+Nk0UlqpTWvK -neoJTrczcbFa7zNQuRQeico6GQQk8MiVkdBVtSrEBKngJe4eiYm0yKCIw6eyWGhlaXLsTFZ6t8HB -D0VZTy5yiDmalKcpbHyFRRFpJFHVeuKJnzyX1iMzae0XQhM/OZPWAzNk6ROHZ04zZMUhdhMxzMQh -haW1oskGFB5c2J22RtvTU96QVTBhGkpo2aJWObJcIM6zvs7WeJIMKFdks3liWYfoFJgLvPYco7XQ -r+5CQMSIOW85uoD+qHZld0VhEWUFHyprH9rxPBVi0AKnLyeSWK00hLIoSawE2FDAQ11fgYYZQnOe -XMFFATldedXWQ7trq6uq6avLA3LSCi8OMukxA3vTP0+UiVYmYUJPG36oZ+yuiq5vLp9Krq5Hhr78 -j+/ffLj5L7v57fU3/4YxL9G/H5on/VQiuChF2pMSwQ31diK4ITI4lALWZfL93gXPxMaGlHvhBFG3 -xa97U84WF4CPXS+i2PVOtkGi7nZH8uyB3B3HG3IlpkfycCifxBE52xFdj+l6VJfjujuwl2wkuiAJ -3okR3kyClzMw5UxMq0R4IL1T1o3TBqYtE5MZmVyKq5jHYE0PP5nZJmXSjcYfNHqdAwczz5TOqOfn -wDmjDI0/jGlt4x9mQDoHJj/uFyUADGkNH54A8LzRcMNsuBrThxgNY9NsGY8sgdkCX3K6snVt5zgq -9skKlzGmVA2NmeTUbOvH+ZGGOFr5Ouph3BeJPClMApQCGain/yxI8vPWmIc0125mJrwBPD8pUzCz -BhwzMseYpEXW8KwMXAGL8XdrQlE8V4XQSw6zYt7TY8uMFMoivTgW6ZHCLB2HNLJrUxhVO/Rke5mJ -lQPZ4TGAcgatvCAOjo4SrnGytVpCo2bcnC9MJPi3T/woPMmgShSR5ebRXMkprG3bVpWxgGzyJXs7 -CKz8i/iSI1sI2SHo9P2o5GhVffGYPjY55BNHdIZ1egn3dY5vAsb0YeP54CR3yOD0DKPpXOyOE+7s -WFZJmrt/hDE9Az59hlFFbqnnHVUY00es0EqY/BJzAJxxUtKNcImP5EWcJMcuXHDU2v0JLl+/TU9c -eply43w6eH06eH06eH06eH06eH06eD2xxk8Hr08Hr08Hr1+Mkv7p4PXLG9N/1IPX6WPV+WPZiYMX -XPnqj3e3X9y/uf3w5va7X//aH8j8Oy9f/PEHeq/m9764/vDh5v4Wzmq/vX/zend3u8P/4yEt+jc8 -w1XT9vq/vr6qCvkfnoT+6auP/LylJDv9lx/53/8d/v5fcPU/ds3uD7u//s9i9/olXv+XP1H7o0rf -uUtS8+73eC2+W3RNPv77dW106Zaf4nPxNFaYaxM9jerhLAd0PrKnsVdXIzpm0Tc6UGbO3b9ccyV/ -4ko0o2lnlI/CaXhFbIfWARN/vNv9x0t7tLq76oqii1ralHCjUlpVtlewCtr4YtVdgRrb7eryqu7T -i/T/spOL8BAlern9xeTedO1r/EU3Kcod3bSWp6KLVdSJ9M2qja7RHeA7dMemTC7Ks/G38anr+Fp6 -5+SZqrij5FrUJ01/heCk+GLbX7Xl2MYdpRejPkEf+xhfSu4cHolmWp10U2b6dT3MoT7uJru576bo -ibRLugpm1hBfS+8cnknuQbdsu+2Vo/Mpuqj3oFumF+nhumQ+RReTe6/nE44NnHy2B08n1HqWQyNx -aIYhvegfQGZUdC2992pGRV0l16Je0RkVXdShirpKL0YPwDNq/UxVpqN0XKOOkhkV9Q== - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - l4Wfk5tI3Ru/XvCoBHX8fH/AHa+BhYzBZnEdEaUZWmsfl1RdUI130fEWmIgknZ8vXKmPnowsO7bo -0ddWdxgyp/NOSVdMXBZwWaYd/fXTY34TsTt3ev1Gn4JcBOQPHJ0/10VpvzfY24TNQi8lKDJhtzZo -nV/U6ioL2vEPLDiqI2asMNV9B26ouCzvMrHb/Gvlx8W3Ne3DrmbYjV+634kJ6Sdf9x5utAoRgQSl -AmmB2KOhqgrr1P8mZqEbn5RzuzIoPd7rgSU2kPvG8igxzdoJbre5XlbzuOQbd94nt2BTxT+8tChq -Q1U6cOO/GUey6rlNIjgJg9xZny5z4yLbOpUfbgtVhritSvvagTz7HU0XdN7QLt+uCLhx3Rt31lmv -355rXjEak4jfUd6qwyvpfNWqFF4Us3oZB76DxUchDG4jdOVw++YLi9tQckX9dcL6+bqyGye9clzh -fOu3ygK2QRS4TUe/3iZ+rvuRm65i15U7CmzrQT++Atevn8UJlHfp4l7fRMMVYfr5itkri3znKtjW -qh/vCDgmOZHNHGUTJJ39vHHQN7nS9tGBXZO1PPgNCQDCfxxrrRO5hR95tJO1ye5Er1ZPVgRnEFLe -WZ9S3MYitwle5nxZ2ZUv6xhynr5e5vdUDmESy7p4Uw7W1Svz2pavXG5bq9LgDmQa3JF3QfJtsfrt -ipQrX7/eADbx9fNz3StScUKTXmZdBR2vUiVr+IsIREJpOhCqi3p9czSdzzavK3O+sjPF5Pa5iU7r -QKbRrXi2z9cN0YF20pUp7UCd/Pr5lW42trtdRHceK8BtNP38CnxPBvs0b1Dmput8fROhV9Tb5+sm -3bnoe3fLtm79fEfGMc+TCavBY2VumxFEYUC/wgaWAl643mC/I6vbOn+eEiH8kZabd16tePn61hIv -E7tM/8rRj2EndpXkN/wacEXcDcG3WZ493hD4CfOC8NINgVeGfWXtyks3ROiMtw24LuMdwts+PiY5 -0a2T2dBtwBW11z24Turs88ZHPm53OJHzunLPbYFXPnvnyHLIt8+1xYbI69quWLhu4q9v6H19s+N3 -ras7u7pyeQFuW3bd3H13PmeVVM65nYg7j33vFvo2SV036Bx9kUPobqKrSFyQSnuxQ4/AEldJzr38 -lXAH9dmw4KcfE01al+DCpJX8za/4q/zyH794UHhfF963KhHvGAa+ZWa8WCPfMVpug16U8X/gyXtb -Hf9BFzxLZTiggq6o+++JRARtg4nKjdPwX3L4jqa4kUAq07FD3mCjK8bboeNuh/ubWv7NHPCe2eCb -5sebnfKmhQvWzItVzMUFSlFFVC8ZFnwVW17+uTGS9ZBNJMNVCC7IZMhoOtR+dQo/GDyv5sCbAe89 -Q9837QhXg8PFMKFo0jUKz/9SRQMAsozl69lc7VnfcYR/tR6ETUq+KT83JemqOb6jYn7T+HUzkl21 -Lj2EKmM3vi2JPvTinDEFyqwlhMDoSlFTOtC7H+EmzN0xc9/P3m7cuilVV/Xrqha+q0B+2zZ2taJd -VbrzCGY+NhpsInairH/snH6iNSpJCvZelkiVqsJhZAf9fhR3rF2NaDc709XY865Z6Nv66V2Tvem8 -/3As3/h+F7UOfyqjbhqsojedIk/NcYUbEKWxRICqtcLvk0oNnndmP6GbTesmrd6k2psS8Z628U1b -yc2mcpXlFW8ztEmsRV340Ep0RQSE44Cawmw34YZ0NT+dksWpRmzuzG9bD652hnctEt9WTa5KzFXd -+YcPCWIf4GWnReF1O0Hr2t/V/r+tENxVh/eUjN1AdLN8EJY+ESRSbkEi5ZcvHs8C9kL/RUFUvLDC -AP7DY9wPhYqcPb9uQOldD+g3wNLF11u/F2Ul2Rkk1s0RVWIPKcTyRfJ0ZHm0rNgvuXd6+sM7m7AA -4zgOTkfr48M3YPrx11uPi+LxgaPOGVP+MkkFbhMaLR5nqcBtRmef6zQ/hkrtsV8mOZDyOJ8rsF/m -2M8pptuupduuxdu2xdu+nX0+Yd/Kbd/Kbd/ybd/ybd/OPj+/b+Wyb+W2b/m2b/myb+Wtfau3fau3 -fSu3fSu3fTv7fMK+9du+9du+tdu+tdu+nX1+ft/6Zd/6bd/abd/aZd/6W/uGJ5AvbNJd+eSVUV45 -5dnrMqD8S76x5XJhwTdYvrHl/Hy2nG9sudw48BWYb2z5Ns3PseV448vpxoKvwHhjzPFNzpxvnLnc -mPAVmG+c+YlbV25bV25bl29bl29bd/b5PM4cb6w53bjwFRhvvPlx6+pt6+pt68pt68pt684+n8ic -8405lxsfvgLzjTk/b+v6bev6bevabevabevOPi/8OV/5c7mx4iswX/nzI1qUm7Ybf+Z57rz4Bms3 -/tyez5/bjT9vEzLh8wZsN/58m+bn+HO58ed6Y8VXYLnx5/Imf243/rwtP952Lt627uzzify53fjz -NqN827p827qzz+fx53Ljz/XGiq/AcuPPj1tXb1tXb1tXbltXblt39vlE/txu/HmbUbttXbtt3dnn -8/hzufHnemPFV2C58efHrTNO2q78eWeOV455ZZlnr3NA43L03amM83enNn6Fagdfb73O8cptvHId -L1/Hy7fxzl7neP02Xr+O167jtdt4Z68rmQUL/2My4mPQ5P97dnUeDmOLgDLDuBKmBr+xaU08xZE8 -xbYfTG6v627s6hM3uMFuasYKI8Pi1Vb333gBf/ndi86XaV8NGFfDk5HmzdxwTjw9deLtMvGrBn+1 -vJTLxNtbE69Pnbj3l5kb8NvGh36Z+tnnQi2nXfZzkz8NlRuZ5wtJ32DfNn1uhJ6eSujxRunf1uWv -Ztur+W+j9efM/TRXbcT+bWX2arq8msA2cn/O3I0045Xev63PXQ14D71eKP458z9tQBvFtwt132Df -tiptFF+fSvHlRvHf1o6uFrGrWWWj+OfM/bQBbBT/bfXgahK62hU2in/O3I02y5Xivy0hX60iD71e -KP458z+16o3iZRqH3HIDfltT38UZdSc+h+i3yefr5E14uUK/ra3uQs1zpn/qVhvd7xMt1+lfle2r -xraLNs+ZvhHpNv8pnWxT7dcFXFXOh35vAs47S/jeID1eDEeAqrJ3sZhtnhlR9W6wVbE8+ns08awD -pdtA8TZSvAyV3hqqXoaqt6HKbahyGaq+NdRUlm8a9I6sKwZvKHRvjqaerdtupcvO3GDxtl1vu0zi -bcPSbW+uwHjbsbet/PG2Z+m2PVdgvG3aO4bpeN22dNuhKzBe9+0yoJq8bxtXL5t0g5Xbxr1tSy23 -jau3PboCy23j3jb/ldvG1dseXYHltnHvWKzKdePqbY+uwHLduMuAehRXi47xx812o8f7BtxMN2ef -Fx65jpauo8XrcPE23tnrhVGu49XreOU6XrmNd/Z645YbOt0dn3eEXjF69juH/EScWr3FqdVfvvg4 -rlhEB0bcp/wGaGqF/9s/XtDo7Pd1BUrnJkSsI+5A/fzrrU8CHtllGl1rGQCaPPIyswPI4M4RoyJF -FxwVevws9LbWIEJDTeUzScTSp5qVhpBQ5sj5VHuvJX/Rp1JtoPAFAYwRr47z+3w0Lnht6vP3j3QM -jkbaB9SQNP6rvxV/P64DWaMx/8z1DKkTReU20tnomO2+nh/nQTh2dcXeTNOwImzLnnxJHGO652mc -c9tIp2WOlPV43zTMVV+xcDaymdYvvqM8ohKUS1+Sc0m+OlrVL8HN1bl9iP0fH2Z5Q/OvP0OqOYxJ -tXAl1c+kVmq/C7HaULz9uY212LiFs0+3he0t9HMlQf29oe9sNNh7zOUbJ+JsdE50W8rnUa5zuNK3 -VpdM6nZo0jX1KNHoWXWXwHnae7/bVipQKSilL+MOmFi7YvFsZDMWCtbfO50/tIqDcMo3jtTZ6Jzv -dcM+hX7t6cqc1UWaLF9w+lSb5adY7hxn+ez9LhRvQzEhKVEqWC+lbW1nI/3v9WPjGmcjDDhO7Hun -62xzTHZfzufRLlN4NtEfK3tdYEpC1X9xbblP7hg8Gul0hZbl507wR5vcx7+V+P7ROhsdc71t1KfQ -fkpDn71MT5p9XYG6CsKImxwOaIu9pR0VZyubquDTZLYN6WcrG/O93T0bPUz4dhI/hXdldkQEPSx4 -138BZ8su7YIcXUU1mSSnnysb1N+bJKdAGxP3pr/JV2cLEdC0AxUvtlHORudUz/U8CnILR3zCiT8n -uYlzerxUUuOhL5jYG5hcNbhA92+Is2cjFtW4A7f2vf7Dw8RuKH6KBPdIabi7pHIP5X5f8iI1vy1i -nHD0uFGoAnXbWTjTQYVvbavaW9g0mfQuQtXRQqWyd4/A2ehhlsdSnie9XYja0nqv+atF2amI1Xt3 -2+4ZUIhG5TEe84K6vYHJTEKodzXwbCSC2JsnZm9wTvG6O0+R02507VVBXtNWH2ocWHUHrmWwd7sR -twKVbDYdowe7H7bVnY1swky/FwHqbCES2HuH6GxzzvRcztMEtQt9S2GiseEiN2hE4ctSGqxYPvJa -OupY2iqmKQWpBMYD37C3NVBJScj3JjgdTXSgN0/R3uCY3m1zniKZPfd2PCl1k89M+BTJS64olc4W -VOwtTDwSTF7FpbPRKtJe93Nv8DDH25F7ikg2/ktW0mfRtva7mSZtMJGeVFbTwZVf6u9NoFOgiVaA -xzdErYdGMqJ2ohLHNtLZ6JzuuabPY1+nsfHdJ4qD267aYHJnqbSng18ReTbSTuhyDW9Ix2cjlv20 -C7ePsP/jwyRvG/UU0fBO8VLTIajYorWh0KP6xKtKL2l70kT63SjeBlN9gcQ/HVrY57a2vYWJakLH -V9HtbKTy37vH6mz0MNNjOc8TEm/E/hkb07m8TVpUOlJhUAe/IvJspJ0oHd8VzrORjPjusTobndO9 -btlTJMcr2XMcy8Lom/Ac/KkiujF6qRu297uRvQ0mxLS4UTRqhe6xbXlnI5PihKyvUt1DIxEN3zti -Z5tztueSniY+Xtm8XbLMafyO9KxCvWPI1t+2lzqGUJAKhjrmHXtHo2JViImUb+Ld0WRVdd48V2ej -Y6q3PXqKFPnsa/Uk202ONKO9SIl2o6kkuaHkbKW9KF6vkt7ZaPUHvLnHZ6OH+d7O4maq2o72XxAa -zRX8Cdej28USyAHT5ahjXmWus5FNdIE/CA1nI5Y7tAu3j7D/4zFFm//z7sQNxU90e7n9NuQF6DWn -g16v+rORzXQRCB9l9bPRIsW9uZtno32ycyVPY8PPRrZ06zY+zLNXBqtDXq+Ws5FOc7HGnuzxaLIa -Ut/c0bPRNlFbw/O47xXNn/CuuJ3f8ryVj9pRVW67Lf1sZVNcYxdOHng2WuMN3tzLs9ExW1vKhmaN -cLHYgsTGuc1UOuUwNu89N/7lew2f2wWx+b2+P8DlA26HX2+T3nz7H4nk+ZTDXesJe03QLVN4Ns5h -Okxa3wL4LgS+u4K3onB+pyCZT9DfM0JavtckeU7b2xOjEj32spTga9nK1Y7/c+qMGw== - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 -X9o7rjk/+kUUnXcWu66dqtpQ4fCp2adRohqAJoD5i4GJm3GhwdQPHtgv38XtCyy5IDFUI+iXZgir -ferDW/KLajjbwl70QTtzDkBrybHkofPFWPZeKDDfrmxMp8SJoPKBs5gxi0vYax/YPcHIU95Sb7kf -sIfvmvirNEqBCgqUN0soYgHpDXGZl4bjN0+iEO+cIidISgclu87JhM9eySPGTYB+MHE+P+xMKdos -WdU8yPXShV9z557b9t5du8ZZFMTvICobA4Jz3HdO0L45gdkbWlgRX6nEM5+bWR7qSJo02HyFRmtC -agYcOlFU+eV4PF7n97/7m3/1d//u/I9/+9uf/v4f//J//9d/+uM//dOf//E3t/79n//n//7t2f6v -f//bb3/8+POfvqn528P+rf2b35VvfydVPH/459/9zV/kH+q3ov/7w/+Tf/v3j3/6Pw/bP38b3/7D -t//238u3Pz3+4g//Wd9Bewp48ZI3sosqXj6ZBF0oeKf3+MMvTPaHv8kP/8fH/0mQ7bHLC+ncGY8Z -ruxz8zFLRNCoCuzBu/pH/NoppLKDr66dmKiP9nKmdVz2zb6txUFzd8cvYNpf+gUx/g== - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 63PYx4XBtre+9a3TpvUjc+vbZ5f+7+/90A9/8N8dPvi1t9m+T9/2b/7Dj//cR3//Ew8//Ik/+NX/ -+hPf+y3v8NnH+4/94Ec//ld//7+e+NKTT37pif/1D5/5xK//2Ld8teZP993+bT/78b/PPR+//IVH -PvlLH7iPsr2dMf1tv/DwY/16n8//1a9+11fhxxOHH/p4i+r0iU/+1BHs4DX/9sOfeXLw86f+5y99 -643w+WvX+fza5R9r6/9x6f+MN3/bhz/9eN/4vvz4X/7Kd91G47/69m/9uT/5h9zR9MnH/+5PP/L/ -HSzS+rznh37143/1d//0+Bf+5ckvPv7I//zzP/roh/7NW1S2vm//5g8+9LO/9Lt/Gtf3dz/yn3/0 -A9/0tfn6xld1y9tml/6v7/vQhz74bUv3vO2W9k30cX/f7Fo/2uzrtCa8rcR2JFw+SGZDP8ldi+I2 -0vK16o76PtmU9ojyBW9Nf0R9bE2DRIn5tqZDWisz3/qAiWDKoI0qfO+6m+Mm77trZWbpCGQavj6+ -Obi82DsylwhHPGmlast0vKmu7LCeKENgL309y7qcHr9GG7t+E1WM0Mas30aPMBxts3uKqQ9vXjqy -OrM0O3cAcMeBm2+KV/fwtq1qmto2dGW2rSu9fcdmcD0B+73ugbnZo9Bxc3VPcAZscxwTVeohiSqL -fgFFJ9w6HNeDltNNRoYeeX1OuF2DAOlYcif0+DYgNoOPHbkTquxvLgZgJyM9sM12kds2SulGjzUQ -nkxfrkHQjVPkXCc9VvSxkyenv1nskRFm7bLp1T0257yWn/mrerVXdJO+GR3xOCcDnTA2/mFso2uj -Ak0fpBOvJysb8XR/6xAmrXE6aw0CQjlpnDKDrWPfKhQhbx371joSqLg8A61LgjdbR4nEFGqgb97J -rKGKRF6FwYaQP9+M2NZVUY4k37+s9ZDFyA5JvhjrSEv7YtMo1zo/aqUE1bsJnITBWb8AJ06tjPW2 -h4Kbqwz+DgUuKHgCKQdvdBHnXba6xUMe1lIp07hACQSOoZUlj14oioB1CWzkM3ww9THmv/kCwVdM -Xzt865s9ZsD6AgnI4ONK08t7xCgMVZh6w/lvvkAywqxdPZesx+acaZPQYDJpKx8aK5FAUXq2yhBj -pCdLb1CkRqfudADT37wS8JXSN9vh20I1esyAaSUSyNDjiJ2SHg0OzGUrwX/zSsgIs3b1XLIem3Ne -A5Vw+RGF554iVoqitOtErFRVZAgnKjOpQ8QpeeCH9TI9FQeji8GQlWoyWG/W+7ZxkxEdi2t0MerD -2Cm6ipcPc1Zv6Ftm0usCaMDa3wLP/BBPOn9LT7pQFes+K+5N4azu09JMRGzoStCirfltyMytvdby -zHIyGL/ul6KIYEDVKJ7iQIbWXXlQ4RlvQr2UKqj1vgMqKOPqr/iIHtZ9kDOTzho7EFc00gGBq+dd -nz+7KENvEvM5I7Z07RMIspr7eOVYHorzDeROKbgJL5/8XRvTy6Cb7VxJaC/vsQZm1z6BsBuOmJMe -K/k4IRz+O7kS0Aizdtn06h6bcx7Rgl+ikhoivOAX+pp6wANWxQOXVYQxvoxiXSh1fBt5DLQmIW7Q -fjLSKoxkkZ2q36UP0W+1iGuHg6NAk1EM+6OXrGkd4HEpWZNVABjVFrDmKq+jpSh6dwxEwt0Im/mm -2EFZRg56N5HTwtqczGUgX1I4EWnR49tANBvqQ8neFIXJgEnfr6pSNRoDQQ4lAutuM6Ac+hwE3XBI -qPRo6GPbeHwCJo0/jTVvnE207rYx+1GLb9GeaD66lgmg08PpHwTaRD4RxMH0ps95JVSGz7gyhogL -qqw48LH9mJPSu9Z390c9AuxNICvt5poNmSCTimW1iELtQlO/oIFfbxVJ2oSXASGHdF5t4lCr4NQv -YOHXW0WxVqGtX7jbe+DQsFUtWGYHsbU3u7x4ePno0sHekftmDs/1FpcPzvVrM5P28malbp9ZjfL3 -0m1HVxfml+Zev7xy5+EopM+tNL+zMVn55qVDy/0Ccm7Bm3TM/Vvg6xW+KQzSQOT9E4s/wPCvLU2j -5axA5j0yBaG0Yg/M4FEmqqg2Gr71jDUsRybi95zJgFMSJVjYKodPE9xRXGSjE7D2hf7HZUAe21Qy -NRIcn6GN9FzD4RkFIZD0OPyYsUoamwCnJKyQ5yJw6rmeeNZJvUTZ4zIgj21qyDpPI4W7OtOE9F/t -CSyfYiutqDsdeQLenggvSuUInhZRAy/IQIzQ5JiQUJkMyPg7ConaqmZjH7lJYyoCGs3FVny8USVr -kSN/m4yubtKpohKtLpmHI1DMtvHewpVsAiHou6zqHmobb5T/DTeugmKgBrU9Ab2mIjURDXsv3FMc -W8TSxNZMRhbC84DL4IKgWlUxMBiNCQMKKqjHPYT4Peohrgj0PC1wF9AUGuFx5xyvTwgWUxHEj30l -FvEiVJqAkRGz9QpHPO8Jrl0l1QiVR7U+DMgG0bGrUHkCGngW9xDFEFiVGj4tcN5oeGJZON59Fbzj -AZdWMTAejkDA0tXl4dRk0MhgwmoUlbBhwCnRusW1lFo9hda8mHEBq3p2lUGjBfQV0ZKMLa6iFRO9 -gohdnqDysqfGi4WjKKqKgJFYZzsSby3DXSEH0waUrOHkK28HgKWpg4NzuKMKjlDlskDyFM+lkgsD -C+UtASvt/3/23rM9dSRpGH4/z3X5P+BMRhIZDCaDMbaxwTlgAbLBREsws+f+8Pz2t4NCK4twZs7u -nJ1Zjy21qrqrqyt1VXecWBviGCJQ8optYwnpWZKmJYoxIpOID2XywnQqTWPoVIbFpRGnxauQGLBK -ouJDsKIjytRLDA/sGzgB+GEySosPo7DAS/sQzZoIIQq3Z2ji+Vh+zoTFgQCewgIRCDgqLq7xaBLT -Vy98pMgJGksUa4I4XtETiXJUGK1o6XlD+xw4b1FaYWQJDH48NoEuCsvbbceor/jPoKs4YPmFK+Aq -zqZTrreY8VpN7F3+dD2MFgMtVcpHgDKZqJ9HYBQyKa0yIA/oaDiMlSjgH7xIYvC8DQYr0bB4Tgkg -bJiKiheJAqefkRRSAvuGol8fkRYUjRKs43HV/alJvBEJV1lS1u+QneO0pnFY7gINbOCkVNgfS0bC -ui7AtUElxf4CvZMUISTCknkQD0ckJyQRizEiFUDDorzM0GOZaA0TYmL+MbsmFRAgDrg/5jQkuzEf -or32zuVs2gTYFgBhILBDXLpFvtn543KO3iXwu+Z4CX5edb8ApwIGxvt3BX4pDFwX7JT95HjXFXCC -AQ/bvHTht0V2PB4Cx2c+GPbEpu3ZbBxyhV3zhesG2sUhg6YeVwDCID+IwQ8qY3bhMvpA1RQY3aAt -hmGKQv1FFH5x9Sc7VjfFMAw+YCipO7awMYwwpcUAJvdqCqz9gao1kBbjkavV44fd7piT2mIYpl/w -vRk7BiKmwoGXHG+MAcMw+Ko9GE6tO1VZ9nFXMAzTdmdT0PU5cmDMAWIYsPkFy48AzwQgH3wOrLtw -zwKgvdl4Btu3xbEMeyNXyOMKihwMuF7Nvz9hLZhMM2R5zHaI4dCf6AJM+C/+v+LYU9ZdXhe9zMMi -9ij8N0rBf2PoX3Hj7qdgVxYExg7+ZvCYgXcQiUpj/ym4w1q6u6Iy1WGwi8G7vz8NPaUmPIOqY8DP -OCVOAv0TB6/IRYwdnSGCGC8ABx5wPPptrxSjdQy7qJZwiHeASJeCZLgt+MxFh1zSP5B5Qi7Rd93C -KrLomkaUmneP+Ue7B2W2ed8if3/foIow7ZDSnSiMWgL/AMb24P4f3GwOo9+SwENIiHr/7+gwoavM -CRn/+wmpVormXUv8/V1T62Ckfc37FyX7Bxx0MNHAo1y1m+sIpSR+J22jCDt/hM6ns7+m6C9oRYuX -54YuQZeh5R3KAxv7T05+HyqKIdvKcAxGjD4Ba284deEm+DFOhgmJbfzGn7UWLKCS9WcFVhj2VN/w -sxFn/RGD/hhf8WJjCMdyxyqEG4pjdDn3HqVc1FCJ+3ClXTt/uNwEKOS5pF0Itwv4Jq5QE3q7RsMC -/m9/OVyYjcj6YzhYGzJaDx8CUAbv4IRHJznBBE1k+IgicuXBrcCV/+SmV/2+OaWsR46XJJkJTasH -7kbvyh8fYM0E2eEcMDZeRrBQQWJxt6TPXKH2cDF2wDBw0zQcxfuYLvcN2x8uhRVm+5/qNgMDccza -3S6MuWl/I0ZDEFZfZgQrKRAseQZ8I6d34pcIhLG0Q3mYkd8y798u81aVcE5SDBgxmYuBp8UFGeCA -xIOwMAxGqaMGJ5KiHem/T3SW+Nnc1Rqw/dlfeiFEvDQXQioITgWRLDphqZo0MrQr3BP4HnoAHHOx -TR8YfegRLbbpjqd42BRMLxVbzeZsT4KEnwxm/P9J3+En3fESbxUbzH4wmUjA2DwMLNPAAqcZ9Es0 -kkQlLGJyJwX/C2cykkxQ4eTmqlHojXmpk3hq5+yQ/yeFMhhbxOF4/g65HP0tl39lubzmDPyq4nzT -IrbtSG4TYU2UCmlFtfzKVWZ7A3NpTVYbOZTVcs8X0rdiFg42HRUJLPTYMVfrNIEbDMimHjXPfYyB -Lftg+PRR8xSV8KAiGyTjIyoEd50KizYKSa0xXU6Ks/mQE9QagIdbSFynxH3ynPgOns0coWIxqclk -9icA2VwI6j4gVI3hVAsRNq/JzSOUpmuasat6Ac1tViQaqbhEshGjioijmg+nzdlQBPdrexZhQNRo -wqln8T8tQuwswtUPoPj3CRW4OfFTpQq9TZlCbUWYaMfsXJrQ/1OihE4Ekwz96wQpdAe+bNMe/veK -wbXPmfwtDX9Lw3+RNAwmfiFpqD3X9bco/HsukfipMg/ANhR45emf3Hg251xAVrjuWX5uztbwrXOe -lpYhRqwMBqkDidXxy9qMH/6f2cs7drzEX6IOpFx5vjcgEglLQ2E+Zn/gP9VyQwQgRR/l4BtKOlwl -WPILq43YT9YawHuJblNx0PComkh8K+pD1zPnnnnsf0p/MME4E4n9KvqD0l+A81uFbMWaXuf2u9/m -9G9z+h8yp7csV+i/xy79NyXe/K9JUEMj3OndwP9eURn/yaIyqMHwi0hLbaccS0vteLZrP/5D6RqR -YBweF2Kbs6F4HJvnbATDMSbsLHEDHssFN1OSUsI0LdYD4sxpWCHIRKlkQjmciwpGcYrH/2T+huYK -75+jDP/+Uf20Qa2elBL7tyel3ECGR8eq4HWFixVUvzDiCXlyMlUMP3nIr7Lk0H07/5PZhrDQHfwv -LuX7S/XNv3y6dbjkwmEKvQoKl8w1D/HZCtYJUh68WArc5DmBW5xzP9RKowdQ8CxQ0cKcQ2tRLZCF -weyv2rDf56atJf/B9hTdj4Fzf87Gf3L5/wyFC+n4GQpG3WU1NWEXHYqSopA0PFuJfENLbyIME1O9 -YTRGBnwWllrDs/CIF7RBYzos9iYCjzghXjCU+CIcScZVL2ip/wyt6iVjAJ4J65+FKQ== - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - YyFPXoeGiB2c1IOQoN0UAsqsd1lQydPMprYdv9uv0fw+TmAdSEZmwCba8Xt9ShGM2GBGTTRv3pQz -N6A5vJURbXn6Z2QdvRF17CYlcDJOQMQz2ROXM5Xd2AM7J9uhYzdH0TulFoBwo2XfW7Ns9s52HNF9 -x26CxAfpqsBSOWVSMGHsD8gsjuzmqFjLDtJpu2l40n0AJ9BkbEUrDracJztym1tzdxKsvQTn3WHH -bdY2PNrDej4+AE7RR5V/6++w93t+s+o/vDmkETyDQ7URq7r3TmDgLGiQ7TnOUjkxlLMBEUCTwfuS -zjqdPmEr15HYjyKsfPtdn9NEmynAoTguZ5NuQKb7ZR8aCM3XOp4TmgYYwweXx0iLDw3X/GRK2Yl4 -YzynKT1IJBGLEKchZlSuYZ/0iyyd7PlNtfaTEemcpFdj0pPWQvYnlYUK/cIder19zWzmE9I+bzav -xk/WESEIIICmHiaGkEZiFYj5qdT3SrSoJwFLFsMdFvm4U6nboRGMJcYsx2fpKEn4WHzv8oB3NunH -2vGcib7T7cKiHTXQX/cIOXEcpyU/q4p2epyNRRR3ivQ1p8Y7k1nPtiBuzLZYMjrJs+62Z+sOnGr3 -dPv4Ntjx0x7o6JwQ1S5tanXuiq7DJJ3vBn6q79apl85lLPZ3a52jCx6Ka/dvsWG4REBwmTvx/ZFo -DYj23kXPv+fUHYNv3iy3Ck8WuSsvgC4B8VP/1iwfgqpLXpGsBBaFdMHbDTIKd6FA1LX1lIkAGsZq -hJA2rMqCmuwlj6nfwzMx40IL8RFxypsYMlHrpWB/2fwtDY7WAK224i9GmRc+vwnR6Dtms8cby0iS -oq6/DuIyJLZblQrqViOKe7K4J6OXO/h6XEImGquq6MeZP6Q9ebIhS+vRWNENs1DclssasEGFTTAh -ZCGEt2nzcjZ/3b53dXuet18JU30/iXUgGp8EnW7H8/XRBeBvygIw0rm3aJfYkPCUwcdW51G7mLGp -MJvqWE6K6BmGL2WERSlIfdV5N/rY9tku6XhO0vlOvRWaXOPhIdl58ssKcce3IlmIjuUMsY8SNrEX -pTMxmTD2FWwmHcdJylayoXIXztXHDs57FyCDqozyhlNv3m7a3vGc2w3ohViZEM+5zY7ntF16sJD1 -mWgCEFVZ7vzj/DnmRjjOklwqKrUkvAUGcb3ZUEJ3DmLoSmho7nhOUzuzmdMBFPiU3iLZsKgV/Gds -5joU/FGGdbQhPzGvicaTAQ6x0I+AxvvVH9oJTfY6nhP8JcbARSQoKJ8DW5/KRzsRb4znNKUvpdpF -2yHFYJ50lDQT7XieEf56IpmHw+G/xykwq6W7kYamnufcfXs+5I4V/ijt9ZD7pOkzf9Mt/1DuF/qb -66kCNZ9PiEsXZGKqsI/27emVX64yq/B4YAOfQqLRPH+YZDb4viDDik+8l/rnqvrON94Iy2n+cq+x -daRZ2rmAkBngXv41OJ3YGkxS927Y51orR9XSmXwo9qvfPTiuAJOGWQQNqzRHoFknLXVp8U6rS1q8 -sdSc7kFuJ/XFg1NueBjsaFhkbvhHPz6u8S5oWUtcoGho5nZHfN8R7fJAbD+nxMeff/M+eP7gawgW -+SkUc9ktP0gbfN8C/9xH4GalgGVSTqUcvbf3S6yRXDKVz/QGn99u/66gLBu4hIgHbFO9nRrwTRgf -aPGvrb195Pn6KAM8sQIX63EZZdkQTxdtS8XziJKbn1I2Ue9Qch1eVQPRKlvRZo88J4X1DIYWALTm -cYi1PG/Sjz6UU1ZiWTBNIRrjbRN+z/PrYiTwE3Fm2GagwDvQcl4LEPMZjVslYo6wjFhfO8/YqD63 -BnxMdwpgs4T2iBXmcFcLRFO/WFNNcJ1RK5AIpPMpBrx9pcNGlP8nE51aNmh9nFoDLb95c2PTfgyS -3RZa2cYgW0gPNxvdll+Hgr/fWRXceUlJ5fzN+XbHleqUab/jOcNNHEVscuNtdySji3bpg2Vnk+l4 -TnFvwh4Yjd40uMJzRqLy2Y2gs1Z9cg0e9s5uboHbj78exh+FjZPTmeaTj0axPuELMp6pQaYrNiHV -md4MZcdyWl0uqo2iMKTUUdfjt11AGDlM5ll7lpNbUks5W5Zf5UG0WEemfNoz9jwnhvPe5D8h51fa -fQb3zVCLstDBFs33qz8yExqpO5YTugx5jCVkpenFbfCoZzKJTr4bY3hG6OttZJy1kzGleDO4pq2a -2juLpZZwdOg9u1nmwuT+JfeueVVwuyz3J6DXPwyrg/KFCnoprD6NSNs5RSoIF/mFTvF6v5va+SfE -vW4013KCfVRfefjvS4Eq+wLMYogppph4ojDJbhDkMbCRmm6rx06UXp0bvDGGExOZvcrWkXJpeYnj -CklSHvxcdGP4MszR+H7g5yrgTr90Ml9X0NNlYPJQy6ChlCYFNMuknQzam6EVJC3PaCpO9+LIXLbH -BU54ZBKgaRSnzviH73ob0boGVg3eGdfljkLFO8GNoI38viNb9UjemAzIeybfvf+dTHmB5BJu+KLT -j9IAnwMcroCAlm+LubCBZEtiX26V+dCwv4WIBwDyVTPdWhYITfiWVT5R+I2Y8Y9hu3fkcAkj9UKb -b1cy0V1CoxK8Y2g0FQDJllS5dtWI+WOclqHvsL9aLEPkkdR1sfFOzCTRh2kkj/ITxHYctZ/2g5PL -Mw1ZVkkLfO1OJ7x0sbic9iSltwkou+BGSwDvejsnFDQwSCvudRtwz/MTrctPAIcfJ7EOROOToNPt -eE7oBmpTHmnJVWwsB6kzMV/WE2IwyYwthZlUx3JWDg0AsWRrBb7oDch6P/pHpt/tkY7lJJXvtJvF -1m9hSS4nb0kv9oONSNahYzlD7KOEm9hLSaDFCaUPXEU/k47jJGXnowp6KTcs16ecC1D2VEYnlHzQ -hg27es9zbqHbC7EyIZ7zmh3PaZv0aCHrE8EEgIQ2lTqn3UbpOc4SHW/wALoUx+RUqF+mOg8xciU0 -LncsJ/rG3mZOx0/IqfKpah8g52ds5jPQ4XsZ1tGG/MS8pvV3loIpiIVeUhW+W/2hnbBEr2M5wV1i -DJtLJUA+O90gWs+lop2EN8byjNifAg/XFsfQpBy3ujbW2LJTc6a+vec5zeNgIBx4fRT+vKenmXTH -c4LZ0K3aDTwU8YVb9Tru+elEYazlRxP5NRIFavuf0Did2LSIdYz17YmRXy4xq5zeW6msFU6815Z4 -x3OS7SOZEf20m2JPVPSdd7wRlhPdzV5n60i7tG8BMXH2GD/PP6HrMEnlu4Gfa60c1Uvn8qHYr8UR -Z6vA5GF2QcMrTRVo7kkLXlrB0xqTlnA0QadbcWQsG5D4iaAyDNo0vLOg8qO3xdHoVnk1w5uNyt6h -xFV9wzxT3x+pVu+olQOn7jl88454vtIQr4CIh/wY1m/t8IGXM+kt4q1oi/ii6rNEdilgbCAupvUr -CuCa5PYSL21t4CEoZdxBwIfd4cLh0i0+NeM8VP1xwBv3+MNFuQIx6wCDKn/otHiCgiAuPgztKKEo -vWykiM+nDnG6fFNNBlhk4liSpB+uHEMFzeIjYEpvEkpj/a34tJhpylR4q27Gj84Nk0wegF/inZQv -nXCDR+JxJjBBL+XtwHwTCKKKCPkmTpkQ/hWSxlhfJOkY3n6NNng3g3UgGpkCm2nP8PXpRNDCOYNB -4Ta2CxUqOr9TYY3DOzZmaCC9HXXsJmVvAH1TxuZxnCuq7sYemHq/ITpuc/S80ykExvkFyYklfsim -Lw8c0U1HdN/xmyDyUbwqsoJCASJWz/1Rn0Cm0fGbWxAGU7wRNCRlrG0mgu+2NgIrsIp/zsd07ObY -yE669bp0N8Ju1l482sR6Pk4EfJJK+adSqphmJjt2swpAgBhm3EBcCDUlKHeegDsMGml7hnObNiqI -7vJwoTR+V24HZwNmz3CWwZCdeV3Iy3vzmsl4FI9YYdsumHXLPjQQlrN1LCe0DDAGZJEh0pI2JM1z -SWUn4I1wPCP09cRK+bfoUeJie6ITuQ6slRkCc4M9x2kWQ0Q/G4VoSnxR9Msw6Cdj0klZL4elJw2e -uEYqDBf7hd7xE1DozOg/Ie5127mUEuzC+nq2SqyS6/xmjuxmr2maMMlk8nMhMuqSlnuJfqoaP/rD -G+M4zcccFLaONEs7DhDTIOWRX2cwMbYGkxS+G/epnkinXDqVD8V+LdQ/WQMmDrUKFk5pYkBTTFa9 -sjqclYysJmMJONmAAxupOP+nHPAgOrMoTt3vD9+8NqqBcocHUO+l4XeHB1DvRozhEb67/Z5THxl8 -99a113mGFzC9PbbKj4LpPQXMBKctA4KJB+SRVQ0wK9+/tfAD9SwmQMscPshZfW8dAvXU41kH8Z1v -xpoKCnbk+foQAwQmZfNLVL4gvlfkXvkpoJrCm1kaUHJA+YmPPOi1NaBkIQYXM1H84IbMuuc5Kapj -IJe8zgPlh/hWNvpQTmBjaZ9p+ZvhjbH8sviI5fYJMnqDd2TiZkIZaUmI2tS73CDiuWQYRkvTAWm2 -xAXfSgFupdSdmC8fAafLbT/3+dSKEEWZZqgMrjVqB7hZhbJY7Df5il4KImZvJTRabbafq6h8Jhpr -dbP0/dQfAmW/i1a2N4a7iO43tjO/Bs+k31zZaEXrCqpy5s1GbT9QKlN/x3OGpziK2OReoHGHdkiD -JqfrzibT8Zzk4fKpCPTNXLINZfqkpUgWmd9ph2qlAt3Qyfc85xa6vRArE+K5LdDxnOaljzayPuUb -5Oc568XPc258YzxnCQ8wQhwdMfhKY1RT/D5GjPwndbRHjlN7UwBH9DhWY1LaLpNwmz7tJ3ue0yyH -mf0n5PxSsw+4A7yovM5iwLYBeO4Wf2glNHB3LCe4S4zhlpiVJuXo9lrJydyiE/HGeJ6R+zrAiU/o -H4Tcfkpp0Q0ltDNaagzUufc8Z5kNFf68q6d50TXhryMqnY6456T9NSIuXQYq7nnXc20Znty+zN9T -jX5iMhMzhX2oX08XDFVOhQ+k1qHw48nDJJVDj7i01p7sOV+hdb7xRlhOi1F7ja0jzdIqFEIqpEEW -j/42vOw9x0nq3g37XJl8VC2dydcV/WQFmDTMImhYpTkCyzlpp4O2bmh1SUs3mpbTPcjtpIJ8n3Jh -w0hNYzpzYT96a1zHBt2tlgeQb7Mjvu+J/oG4/ZwSH37+3fvi+NiDh1KfBzlxKZ+G/60vPgb5Fs+D -r2E2+yW7gRGLX/T5E51Z3AbUaw2wi4wtYAyZiG94C36eIc9qm+/I8/VRJqM2FZQf95Z0w5LFQaVQ -KjWTlg3J2Vn8pWyiGNxGcy5kml0anuyR5aSojoFsAE6cwkMN/o6YvBt9KCdQ5hC2QDRmUU34Pc+v -C5Gy3C5AyCCJ6hLDZkLalQ+lRqcNC28xNmTDiL4+mCLElD+p4rGJ1F6UAxF5Ll6l0O4OpaczySz2 -A11wrTEzkBE1HjaK+RKW2YhKxUx0ymy/Xrwq33hlA2x2vp/4Y5Ts9tDa74zhFqKbjQ== - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 -l9/8SZzV/wBtC+WQ - ]]> -</i:pgf> -</svg> diff --git a/branding/icons/numpylogoicon.svg b/branding/icons/numpylogoicon.svg deleted file mode 100644 index 840a189a6..000000000 --- a/branding/icons/numpylogoicon.svg +++ /dev/null @@ -1,6967 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 12.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 51448) --> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ - <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/"> - <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/"> - <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/"> - <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/"> - <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/"> - <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/"> - <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/"> - <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/"> - <!ENTITY ns_svg "http://www.w3.org/2000/svg"> - <!ENTITY ns_xlink "http://www.w3.org/1999/xlink"> -]> -<svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;" - xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="287.498" height="307.15" viewBox="0 0 287.498 307.15" - overflow="visible" enable-background="new 0 0 287.498 307.15" xml:space="preserve"> -<switch> - <foreignObject requiredExtensions="&ns_ai;" x="0" y="0" width="1" height="1"> - <i:pgfRef xlink:href="#adobe_illustrator_pgf"> - </i:pgfRef> - </foreignObject> - <g i:extraneous="self"> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="132.798,162.12 133.705,207.377 89.082,199.509 88.175,154.251 "/> - </g> - <g> - <polygon fill="#7A88CC" points="65.68,217.759 65.671,217.294 88.2,199.81 88.209,200.275 "/> - </g> - <g> - <polygon fill="#7684CA" points="66.122,217.837 65.68,217.759 88.209,200.275 88.651,200.353 "/> - </g> - <g> - <path fill="#6272C3" d="M133.229,161.276l0.442,0.078l0.01,0.465l0.925,46.174l0.01,0.465l-0.442-0.078l-45.521-8.026 - l-0.442-0.078L88.2,199.81l-0.926-46.174l-0.01-0.465l0.442,0.078L133.229,161.276z M133.705,207.377l-0.907-45.258 - l-44.623-7.868l0.907,45.258L133.705,207.377"/> - </g> - <g> - <polygon fill="#7A88CC" points="65.671,217.294 64.745,171.121 87.274,153.636 88.2,199.81 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.745,171.121 64.735,170.656 87.265,153.171 87.274,153.636 "/> - </g> - <g> - <polygon fill="#7A88CC" points="65.646,171.736 88.175,154.251 89.082,199.509 66.553,216.994 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="66.553,216.994 65.646,171.736 88.175,154.251 89.082,199.509 "/> - </g> - <g> - <polygon fill="#7684CA" points="64.735,170.656 87.265,153.171 87.707,153.25 65.178,170.734 "/> - </g> - <g> - <polygon fill="#7684CA" points="111.644,225.864 66.122,217.837 88.651,200.353 134.173,208.379 "/> - </g> - <g> - <polygon fill="#7684CA" points="66.553,216.994 89.082,199.509 133.705,207.377 111.176,224.862 "/> - </g> - <g> - <polygon fill="#769AC7" points="111.176,224.862 66.553,216.994 89.082,199.509 133.705,207.377 "/> - </g> - <g> - <polygon fill="#7684CA" points="112.086,225.942 111.644,225.864 134.173,208.379 134.615,208.458 "/> - </g> - <g> - <polygon fill="#7A88CC" points="112.076,225.477 134.605,207.993 134.615,208.458 112.086,225.942 "/> - </g> - <g> - <polygon fill="#628CBE" points="110.269,179.604 111.176,224.862 66.553,216.994 65.646,171.736 "/> - </g> - <g> - <polygon fill="#769AC7" points="65.646,171.736 88.175,154.251 132.798,162.12 110.269,179.604 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.269,179.604 65.646,171.736 88.175,154.251 132.798,162.12 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="110.269,179.604 132.798,162.12 133.705,207.377 111.176,224.862 "/> - </g> - <g> - <polygon fill="#7A88CC" points="111.176,224.862 110.269,179.604 132.798,162.12 133.705,207.377 "/> - </g> - <g> - <polygon fill="#7684CA" points="65.178,170.734 87.707,153.25 133.229,161.276 110.699,178.76 "/> - </g> - <g> - <polygon fill="#7A88CC" points="111.15,179.303 133.681,161.819 134.605,207.993 112.076,225.477 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.699,178.76 133.229,161.276 133.671,161.354 111.142,178.838 "/> - </g> - <g> - <polygon fill="#7A88CC" points="111.142,178.838 133.671,161.354 133.681,161.819 111.15,179.303 "/> - </g> - <g> - <path fill="#6272C3" d="M110.699,178.76l0.442,0.078l0.009,0.465l0.926,46.174l0.01,0.465l-0.442-0.078l-45.521-8.026 - l-0.442-0.078l-0.009-0.465l-0.926-46.174l-0.01-0.465l0.442,0.078L110.699,178.76z M111.176,224.862l-0.907-45.258 - l-44.623-7.868l0.907,45.258L111.176,224.862"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M286.112,189.068l0.441,0.078l0.01,0.465l0.926,46.182l0.009,0.457l-0.442-0.078l-45.521-8.026 - l-0.442-0.078l-0.009-0.457l-0.926-46.182l-0.01-0.465l0.442,0.078L286.112,189.068z M286.588,235.169l-0.907-45.258 - l-44.623-7.868l0.907,45.258L286.588,235.169"/> - </g> - <g> - <polygon fill="#628CBE" points="285.681,189.912 286.588,235.169 241.965,227.301 241.058,182.043 "/> - </g> - <g> - <polygon fill="#7A88CC" points="218.563,245.551 218.554,245.094 241.083,227.61 241.092,228.067 "/> - </g> - <g> - <polygon fill="#7684CA" points="219.005,245.629 218.563,245.551 241.092,228.067 241.534,228.145 "/> - </g> - <g> - <polygon fill="#7A88CC" points="218.554,245.094 217.628,198.913 240.157,181.428 241.083,227.61 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.628,198.913 217.618,198.448 240.147,180.963 240.157,181.428 "/> - </g> - <g> - <polygon fill="#7A88CC" points="218.528,199.528 241.058,182.043 241.965,227.301 219.436,244.786 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="219.436,244.786 218.528,199.528 241.058,182.043 241.965,227.301 "/> - </g> - <g> - <polygon fill="#7684CA" points="217.618,198.448 240.147,180.963 240.59,181.042 218.061,198.526 "/> - </g> - <g> - <polygon fill="#7684CA" points="264.526,253.656 219.005,245.629 241.534,228.145 287.056,236.171 "/> - </g> - <g> - <polygon fill="#769AC7" points="264.059,252.654 219.436,244.786 241.965,227.301 286.588,235.169 "/> - </g> - <g> - <polygon fill="#7684CA" points="219.436,244.786 241.965,227.301 286.588,235.169 264.059,252.654 "/> - </g> - <g> - <polygon fill="#7684CA" points="264.969,253.734 264.526,253.656 287.056,236.171 287.498,236.25 "/> - </g> - <g> - <polygon fill="#7A88CC" points="264.959,253.277 287.489,235.792 287.498,236.25 264.969,253.734 "/> - </g> - <g> - <polygon fill="#628CBE" points="263.151,207.396 264.059,252.654 219.436,244.786 218.528,199.528 "/> - </g> - <g> - <polygon fill="#769AC7" points="218.528,199.528 241.058,182.043 285.681,189.912 263.151,207.396 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.151,207.396 218.528,199.528 241.058,182.043 285.681,189.912 "/> - </g> - <g> - <polygon fill="#7A88CC" points="264.059,252.654 263.151,207.396 285.681,189.912 286.588,235.169 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="263.151,207.396 285.681,189.912 286.588,235.169 264.059,252.654 "/> - </g> - <g> - <polygon fill="#7684CA" points="218.061,198.526 240.59,181.042 286.112,189.068 263.582,206.552 "/> - </g> - <g> - <polygon fill="#7A88CC" points="264.033,207.095 286.563,189.611 287.489,235.792 264.959,253.277 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.582,206.552 286.112,189.068 286.554,189.146 264.024,206.63 "/> - </g> - <g> - <polygon fill="#7A88CC" points="264.024,206.63 286.554,189.146 286.563,189.611 264.033,207.095 "/> - </g> - <g> - <path fill="#6272C3" d="M263.582,206.552l0.442,0.078l0.009,0.465l0.926,46.182l0.01,0.457l-0.442-0.078l-45.521-8.026 - l-0.442-0.078l-0.009-0.457l-0.926-46.182l-0.01-0.465l0.442,0.078L263.582,206.552z M264.059,252.654l-0.907-45.258 - l-44.623-7.868l0.907,45.258L264.059,252.654"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M234.993,179.986l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L234.993,179.986z M235.468,226.08l-0.906-45.251 - l-44.622-7.867l0.907,45.25L235.468,226.08"/> - </g> - <g> - <polygon fill="#628CBE" points="234.562,180.829 235.468,226.08 190.847,218.211 189.939,172.961 "/> - </g> - <g> - <polygon fill="#7A88CC" points="167.443,236.461 167.435,236.004 189.964,218.52 189.974,218.977 "/> - </g> - <g> - <polygon fill="#7684CA" points="167.894,236.541 167.443,236.461 189.974,218.977 190.423,219.056 "/> - </g> - <g> - <polygon fill="#7A88CC" points="167.435,236.004 166.509,189.823 189.038,172.338 189.964,218.52 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.509,189.823 166.5,189.366 189.029,171.881 189.038,172.338 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="168.317,235.696 167.41,190.445 189.939,172.961 190.847,218.211 "/> - </g> - <g> - <polygon fill="#7A88CC" points="167.41,190.445 189.939,172.961 190.847,218.211 168.317,235.696 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.5,189.366 189.029,171.881 189.479,171.96 166.949,189.445 "/> - </g> - <g> - <polygon fill="#7684CA" points="213.408,244.566 167.894,236.541 190.423,219.056 235.938,227.082 "/> - </g> - <g> - <polygon fill="#769AC7" points="212.938,243.564 168.317,235.696 190.847,218.211 235.468,226.08 "/> - </g> - <g> - <polygon fill="#7684CA" points="168.317,235.696 190.847,218.211 235.468,226.08 212.938,243.564 "/> - </g> - <g> - <polygon fill="#7684CA" points="213.857,244.645 213.408,244.566 235.938,227.082 236.387,227.161 "/> - </g> - <g> - <polygon fill="#7A88CC" points="213.849,244.188 236.378,226.704 236.387,227.161 213.857,244.645 "/> - </g> - <g> - <polygon fill="#628CBE" points="212.031,198.313 212.938,243.564 168.317,235.696 167.41,190.445 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.031,198.313 167.41,190.445 189.939,172.961 234.562,180.829 "/> - </g> - <g> - <polygon fill="#769AC7" points="167.41,190.445 189.939,172.961 234.562,180.829 212.031,198.313 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.938,243.564 212.031,198.313 234.562,180.829 235.468,226.08 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="212.031,198.313 234.562,180.829 235.468,226.08 212.938,243.564 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.949,189.445 189.479,171.96 234.993,179.986 212.464,197.47 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.923,198.006 235.452,180.522 236.378,226.704 213.849,244.188 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.464,197.47 234.993,179.986 235.443,180.065 212.913,197.549 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.913,197.549 235.443,180.065 235.452,180.522 212.923,198.006 "/> - </g> - <g> - <path fill="#6272C3" d="M212.464,197.47l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L212.464,197.47z M212.938,243.564l-0.907-45.251 - l-44.621-7.868l0.907,45.251L212.938,243.564"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M183.61,170.627l0.442,0.078l0.01,0.465l0.925,46.174l0.01,0.465l-0.442-0.078l-45.521-8.026 - l-0.442-0.078l-0.009-0.465l-0.926-46.174l-0.01-0.465l0.442,0.078L183.61,170.627z M184.087,216.729l-0.907-45.258 - l-44.623-7.868l0.907,45.258L184.087,216.729"/> - </g> - <g> - <polygon fill="#628CBE" points="183.18,171.471 184.087,216.729 139.464,208.861 138.557,163.603 "/> - </g> - <g> - <polygon fill="#7A88CC" points="116.062,227.111 116.052,226.646 138.582,209.162 138.591,209.626 "/> - </g> - <g> - <polygon fill="#7684CA" points="116.504,227.189 116.062,227.111 138.591,209.626 139.033,209.705 "/> - </g> - <g> - <polygon fill="#7A88CC" points="116.052,226.646 115.127,180.472 137.656,162.988 138.582,209.162 "/> - </g> - <g> - <polygon fill="#7A88CC" points="115.127,180.472 115.117,180.007 137.646,162.523 137.656,162.988 "/> - </g> - <g> - <polygon fill="#7A88CC" points="116.027,181.087 138.557,163.603 139.464,208.861 116.935,226.345 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="116.935,226.345 116.027,181.087 138.557,163.603 139.464,208.861 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.117,180.007 137.646,162.523 138.089,162.601 115.56,180.085 "/> - </g> - <g> - <polygon fill="#7684CA" points="162.025,235.215 116.504,227.189 139.033,209.705 184.555,217.731 "/> - </g> - <g> - <polygon fill="#769AC7" points="161.558,234.213 116.935,226.345 139.464,208.861 184.087,216.729 "/> - </g> - <g> - <polygon fill="#7684CA" points="116.935,226.345 139.464,208.861 184.087,216.729 161.558,234.213 "/> - </g> - <g> - <polygon fill="#7684CA" points="162.468,235.293 162.025,235.215 184.555,217.731 184.997,217.809 "/> - </g> - <g> - <polygon fill="#7A88CC" points="162.458,234.829 184.987,217.344 184.997,217.809 162.468,235.293 "/> - </g> - <g> - <polygon fill="#628CBE" points="160.65,188.956 161.558,234.213 116.935,226.345 116.027,181.087 "/> - </g> - <g> - <polygon fill="#769AC7" points="116.027,181.087 138.557,163.603 183.18,171.471 160.65,188.956 "/> - </g> - <g> - <polygon fill="#7684CA" points="160.65,188.956 116.027,181.087 138.557,163.603 183.18,171.471 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.558,234.213 160.65,188.956 183.18,171.471 184.087,216.729 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="160.65,188.956 183.18,171.471 184.087,216.729 161.558,234.213 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.56,180.085 138.089,162.601 183.61,170.627 161.081,188.112 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.532,188.655 184.063,171.17 184.987,217.344 162.458,234.829 "/> - </g> - <g> - <polygon fill="#7684CA" points="161.081,188.112 183.61,170.627 184.053,170.706 161.523,188.19 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.523,188.19 184.053,170.706 184.063,171.17 161.532,188.655 "/> - </g> - <g> - <path fill="#6272C3" d="M161.081,188.112l0.442,0.078l0.009,0.465l0.926,46.174l0.01,0.465l-0.442-0.078l-45.521-8.026 - l-0.442-0.078l-0.01-0.465l-0.925-46.174l-0.01-0.465l0.442,0.078L161.081,188.112z M161.558,234.213l-0.907-45.258 - l-44.623-7.868l0.907,45.258L161.558,234.213"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="131.799,111.125 132.706,156.375 88.083,148.507 87.176,103.256 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.681,166.757 64.672,166.299 87.201,148.815 87.21,149.273 "/> - </g> - <g> - <polygon fill="#7684CA" points="65.122,166.834 64.681,166.757 87.21,149.273 87.652,149.35 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.672,166.299 63.746,120.119 86.275,102.634 87.201,148.815 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.646,120.741 87.176,103.256 88.083,148.507 65.554,165.992 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="65.554,165.992 64.646,120.741 87.176,103.256 88.083,148.507 "/> - </g> - <g> - <polygon fill="#6272C3" points="133.606,156.998 132.681,110.817 132.672,110.359 132.229,110.282 86.708,102.254 - 86.266,102.177 86.275,102.634 86.302,103.935 87.176,103.256 131.799,111.125 132.706,156.375 88.083,148.507 87.208,149.186 - 87.21,149.273 87.652,149.35 133.174,157.377 133.616,157.456 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.746,120.119 63.736,119.661 86.266,102.177 86.275,102.634 "/> - </g> - <g> - <polygon fill="#7684CA" points="63.736,119.661 86.266,102.177 86.708,102.254 64.179,119.739 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.645,174.862 65.122,166.834 87.652,149.35 133.174,157.377 "/> - </g> - <g> - <polygon fill="#7684CA" points="65.554,165.992 88.083,148.507 132.706,156.375 110.177,173.86 "/> - </g> - <g> - <polygon fill="#769AC7" points="110.177,173.86 65.554,165.992 88.083,148.507 132.706,156.375 "/> - </g> - <g> - <polygon fill="#7684CA" points="111.087,174.94 110.645,174.862 133.174,157.377 133.616,157.456 "/> - </g> - <g> - <polygon fill="#7A88CC" points="111.077,174.482 133.606,156.998 133.616,157.456 111.087,174.94 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.27,128.609 64.646,120.741 87.176,103.256 131.799,111.125 "/> - </g> - <g> - <polygon fill="#7A88CC" points="110.177,173.86 109.27,128.609 131.799,111.125 132.706,156.375 "/> - </g> - <g> - <polygon fill="#769AC7" points="64.646,120.741 87.176,103.256 131.799,111.125 109.27,128.609 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="109.27,128.609 131.799,111.125 132.706,156.375 110.177,173.86 "/> - </g> - <g> - <path fill="#6272C3" d="M109.7,127.766l0.442,0.077l0.009,0.458l0.926,46.181l0.01,0.458l-0.442-0.078l-45.522-8.027 - l-0.441-0.077l-0.009-0.458l-0.926-46.181l-0.01-0.458l0.442,0.078L109.7,127.766z M110.177,173.86l-0.907-45.251 - l-44.623-7.868l0.907,45.251L110.177,173.86"/> - </g> - <g> - <polygon fill="#628CBE" points="109.27,128.609 110.177,173.86 65.554,165.992 64.646,120.741 "/> - </g> - <g> - <polygon fill="#7684CA" points="64.179,119.739 86.708,102.254 132.229,110.282 109.7,127.766 "/> - </g> - <g> - <polygon fill="#7A88CC" points="110.151,128.301 132.681,110.817 133.606,156.998 111.077,174.482 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.7,127.766 132.229,110.282 132.672,110.359 110.143,127.843 "/> - </g> - <g> - <polygon fill="#7A88CC" points="110.143,127.843 132.672,110.359 132.681,110.817 110.151,128.301 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="284.682,138.917 285.589,184.167 240.966,176.299 240.059,131.048 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.563,194.549 217.554,194.091 240.084,176.607 240.093,177.065 "/> - </g> - <g> - <polygon fill="#7684CA" points="218.005,194.626 217.563,194.549 240.093,177.065 240.535,177.142 "/> - </g> - <g> - <path fill="#6272C3" d="M285.112,138.074l0.442,0.077l0.009,0.458l0.926,46.181l0.01,0.458l-0.442-0.078l-45.521-8.027 - l-0.442-0.077l-0.009-0.458l-0.926-46.181l-0.01-0.458l0.442,0.078L285.112,138.074z M285.589,184.167l-0.907-45.251 - l-44.623-7.868l0.907,45.251L285.589,184.167"/> - </g> - <g> - <polygon fill="#7A88CC" points="217.554,194.091 216.629,147.911 239.158,130.426 240.084,176.607 "/> - </g> - <g> - <polygon fill="#7A88CC" points="216.629,147.911 216.619,147.453 239.148,129.968 239.158,130.426 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.529,148.533 240.059,131.048 240.966,176.299 218.437,193.784 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="218.437,193.784 217.529,148.533 240.059,131.048 240.966,176.299 "/> - </g> - <g> - <polygon fill="#7684CA" points="216.619,147.453 239.148,129.968 239.591,130.046 217.062,147.531 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.527,202.654 218.005,194.626 240.535,177.142 286.057,185.169 "/> - </g> - <g> - <polygon fill="#769AC7" points="263.06,201.652 218.437,193.784 240.966,176.299 285.589,184.167 "/> - </g> - <g> - <polygon fill="#7684CA" points="218.437,193.784 240.966,176.299 285.589,184.167 263.06,201.652 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.97,202.732 263.527,202.654 286.057,185.169 286.499,185.248 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.96,202.274 286.489,184.79 286.499,185.248 263.97,202.732 "/> - </g> - <g> - <polygon fill="#769AC7" points="217.529,148.533 240.059,131.048 284.682,138.917 262.152,156.401 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.152,156.401 217.529,148.533 240.059,131.048 284.682,138.917 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.06,201.652 262.152,156.401 284.682,138.917 285.589,184.167 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="262.152,156.401 284.682,138.917 285.589,184.167 263.06,201.652 "/> - </g> - <g> - <path fill="#6272C3" d="M262.583,155.558l0.442,0.077l0.009,0.458l0.926,46.181l0.01,0.458l-0.442-0.078l-45.522-8.027 - l-0.441-0.077l-0.01-0.458l-0.925-46.181l-0.01-0.458l0.442,0.078L262.583,155.558z M263.06,201.652l-0.907-45.251 - l-44.623-7.868l0.907,45.251L263.06,201.652"/> - </g> - <g> - <polygon fill="#628CBE" points="262.152,156.401 263.06,201.652 218.437,193.784 217.529,148.533 "/> - </g> - <g> - <polygon fill="#7684CA" points="217.062,147.531 239.591,130.046 285.112,138.074 262.583,155.558 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.034,156.093 285.563,138.609 286.489,184.79 263.96,202.274 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.583,155.558 285.112,138.074 285.555,138.151 263.025,155.635 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.025,155.635 285.555,138.151 285.563,138.609 263.034,156.093 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="233.562,129.828 234.469,175.085 189.847,167.218 188.939,121.959 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.444,185.467 166.435,185.002 188.965,167.518 188.974,167.983 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.886,185.545 166.444,185.467 188.974,167.983 189.415,168.061 "/> - </g> - <g> - <path fill="#6272C3" d="M233.993,128.984l0.441,0.078l0.009,0.457l0.926,46.182l0.01,0.465l-0.441-0.078l-45.522-8.026 - l-0.441-0.078l-0.009-0.465l-0.926-46.182l-0.01-0.457l0.441,0.077L233.993,128.984z M234.469,175.085l-0.907-45.258 - l-44.622-7.868l0.907,45.259L234.469,175.085"/> - </g> - <g> - <polygon fill="#7A88CC" points="166.435,185.002 165.509,138.821 188.039,121.336 188.965,167.518 "/> - </g> - <g> - <polygon fill="#7A88CC" points="165.509,138.821 165.5,138.364 188.029,120.879 188.039,121.336 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="167.317,184.702 166.41,139.444 188.939,121.959 189.847,167.218 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.41,139.444 188.939,121.959 189.847,167.218 167.317,184.703 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.5,138.364 188.029,120.879 188.471,120.957 165.941,138.441 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.408,193.572 166.886,185.545 189.415,168.061 234.938,176.087 "/> - </g> - <g> - <polygon fill="#7684CA" points="167.317,184.703 189.847,167.218 234.469,175.085 211.939,192.57 "/> - </g> - <g> - <polygon fill="#769AC7" points="211.939,192.57 167.317,184.702 189.847,167.218 234.469,175.085 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.85,193.65 212.408,193.572 234.938,176.087 235.379,176.166 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.84,193.185 235.369,175.701 235.379,176.166 212.85,193.65 "/> - </g> - <g> - <polygon fill="#628CBE" points="211.032,147.312 211.939,192.57 167.317,184.702 166.41,139.444 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.032,147.312 166.41,139.444 188.939,121.959 233.562,129.828 "/> - </g> - <g> - <polygon fill="#769AC7" points="166.41,139.444 188.939,121.959 233.562,129.828 211.032,147.312 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.939,192.57 211.032,147.312 233.562,129.828 234.469,175.085 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="211.032,147.312 233.562,129.828 234.469,175.085 211.939,192.57 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.941,138.441 188.471,120.957 233.993,128.984 211.464,146.468 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.914,147.003 234.443,129.519 235.369,175.701 212.84,193.185 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.464,146.468 233.993,128.984 234.435,129.062 211.905,146.546 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.905,146.546 234.435,129.062 234.443,129.519 211.914,147.003 "/> - </g> - <g> - <path fill="#6272C3" d="M211.464,146.468l0.441,0.078l0.009,0.457l0.926,46.182l0.01,0.465l-0.441-0.078l-45.522-8.026 - l-0.441-0.078l-0.01-0.465l-0.926-46.182l-0.009-0.457l0.441,0.077L211.464,146.468z M211.939,192.57l-0.907-45.258 - l-44.622-7.868l0.907,45.259L211.939,192.57"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="182.181,120.476 183.088,165.727 138.465,157.859 137.558,112.608 "/> - </g> - <g> - <polygon fill="#7A88CC" points="115.063,176.109 115.054,175.651 137.583,158.167 137.592,158.625 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.504,176.186 115.063,176.109 137.592,158.625 138.034,158.702 "/> - </g> - <g> - <polygon fill="#7A88CC" points="115.054,175.651 114.128,129.47 136.657,111.986 137.583,158.167 "/> - </g> - <g> - <polygon fill="#7A88CC" points="115.028,130.092 137.558,112.608 138.465,157.859 115.936,175.343 "/> - </g> - <g> - <polygon fill="#7684CA" points="161.026,184.213 115.504,176.186 138.034,158.702 183.556,166.729 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.936,175.343 138.465,157.859 183.088,165.727 160.559,183.211 "/> - </g> - <g> - <polygon fill="#769AC7" points="160.559,183.211 115.936,175.343 138.465,157.859 183.088,165.727 "/> - </g> - <g> - <polygon fill="#6272C3" points="183.988,166.349 183.063,120.168 183.054,119.71 182.611,119.633 137.09,111.606 - 136.647,111.529 136.657,111.986 136.683,113.288 137.558,112.608 182.181,120.476 183.088,165.727 182.122,166.476 - 183.556,166.729 183.998,166.807 "/> - </g> - <g> - <polygon fill="#7A88CC" points="114.128,129.47 114.118,129.012 136.647,111.529 136.657,111.986 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="115.936,175.343 115.028,130.092 137.558,112.608 138.465,157.859 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.118,129.012 136.647,111.529 137.09,111.606 114.561,129.09 "/> - </g> - <g> - <polygon fill="#7684CA" points="161.469,184.292 161.026,184.213 183.556,166.729 183.998,166.807 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.459,183.833 183.988,166.349 183.998,166.807 161.469,184.292 "/> - </g> - <g> - <polygon fill="#628CBE" points="159.651,137.96 160.559,183.211 115.936,175.343 115.028,130.092 "/> - </g> - <g> - <polygon fill="#7684CA" points="159.651,137.96 115.028,130.092 137.558,112.608 182.181,120.476 "/> - </g> - <g> - <polygon fill="#769AC7" points="115.028,130.092 137.558,112.608 182.181,120.476 159.651,137.96 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.559,183.211 159.651,137.96 182.181,120.476 183.088,165.727 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="159.651,137.96 182.181,120.476 183.088,165.727 160.559,183.211 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.561,129.09 137.09,111.606 182.611,119.633 160.082,137.118 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.533,137.653 183.063,120.168 183.988,166.349 161.459,183.833 "/> - </g> - <g> - <polygon fill="#7684CA" points="160.082,137.118 182.611,119.633 183.054,119.71 160.524,137.195 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.524,137.195 183.054,119.71 183.063,120.168 160.533,137.653 "/> - </g> - <g> - <path fill="#6272C3" d="M160.082,137.118l0.442,0.077l0.009,0.458l0.926,46.181l0.01,0.458l-0.442-0.078l-45.522-8.027 - l-0.441-0.077l-0.009-0.458l-0.926-46.181l-0.01-0.458l0.442,0.078L160.082,137.118z M160.559,183.211l-0.907-45.251 - l-44.623-7.868l0.907,45.251L160.559,183.211"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="131.385,59.942 132.292,105.201 87.67,97.333 86.763,52.074 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.268,115.582 64.258,115.117 86.787,97.632 86.797,98.098 "/> - </g> - <g> - <polygon fill="#7684CA" points="64.709,115.66 64.268,115.582 86.797,98.098 87.238,98.175 "/> - </g> - <g> - <path fill="#6272C3" d="M131.816,59.098l0.45,0.079l0.009,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.522-8.027 - l-0.441-0.077l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.441,0.078L131.816,59.098z M132.292,105.201l-0.907-45.259 - l-44.622-7.868l0.907,45.259L132.292,105.201"/> - </g> - <g> - <polygon fill="#7A88CC" points="64.258,115.117 63.332,68.944 85.861,51.459 86.787,97.632 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.332,68.944 63.323,68.478 85.853,50.994 85.861,51.459 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="65.141,114.817 64.233,69.558 86.763,52.074 87.67,97.333 "/> - </g> - <g> - <polygon fill="#7A88CC" points="64.233,69.558 86.763,52.074 87.67,97.333 65.141,114.817 "/> - </g> - <g> - <polygon fill="#7684CA" points="63.323,68.478 85.853,50.994 86.294,51.072 63.765,68.556 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.231,123.687 64.709,115.66 87.238,98.175 132.761,106.203 "/> - </g> - <g> - <polygon fill="#7684CA" points="65.141,114.817 87.67,97.333 132.292,105.201 109.763,122.685 "/> - </g> - <g> - <polygon fill="#769AC7" points="109.763,122.685 65.141,114.817 87.67,97.333 132.292,105.201 "/> - </g> - <g> - <polygon fill="#7684CA" points="110.681,123.766 110.231,123.687 132.761,106.203 133.21,106.282 "/> - </g> - <g> - <polygon fill="#7A88CC" points="110.672,123.3 133.201,105.816 133.21,106.282 110.681,123.766 "/> - </g> - <g> - <polygon fill="#7684CA" points="108.855,77.426 64.233,69.558 86.763,52.074 131.385,59.942 "/> - </g> - <g> - <polygon fill="#769AC7" points="64.233,69.558 86.763,52.074 131.385,59.942 108.855,77.426 "/> - </g> - <g> - <polygon fill="#7A88CC" points="109.763,122.685 108.855,77.426 131.385,59.942 132.292,105.201 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="108.855,77.426 131.385,59.942 132.292,105.201 109.763,122.685 "/> - </g> - <g> - <path fill="#6272C3" d="M109.287,76.583l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.522-8.027 - l-0.441-0.078l-0.01-0.465l-0.926-46.173l-0.009-0.466l0.441,0.078L109.287,76.583z M109.763,122.685l-0.907-45.259 - l-44.622-7.868l0.907,45.259L109.763,122.685"/> - </g> - <g> - <path fill="#628CBE" d="M108.855,77.426l-44.622-7.868l0.907,45.259l44.622,7.868L108.855,77.426z M64.233,69.558 - L64.233,69.558L64.233,69.558L64.233,69.558z"/> - </g> - <g> - <polygon fill="#7684CA" points="63.765,68.556 86.294,51.072 131.816,59.098 109.287,76.583 "/> - </g> - <g> - <polygon fill="#7A88CC" points="109.746,77.127 132.275,59.643 133.201,105.816 110.672,123.3 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.287,76.583 131.816,59.098 132.267,59.177 109.736,76.662 "/> - </g> - <g> - <polygon fill="#7A88CC" points="109.736,76.662 132.267,59.177 132.275,59.643 109.746,77.127 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="284.269,87.742 285.175,132.993 240.553,125.125 239.646,79.874 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.151,143.375 217.142,142.917 239.671,125.432 239.681,125.89 "/> - </g> - <g> - <polygon fill="#7684CA" points="217.592,143.452 217.151,143.375 239.681,125.89 240.121,125.967 "/> - </g> - <g> - <path fill="#6272C3" d="M284.7,86.89l0.449,0.08l0.01,0.465l0.926,46.182l0.009,0.457l-0.449-0.079l-45.523-8.027l-0.44-0.077 - l-0.01-0.458l-0.926-46.181l-0.009-0.466l0.44,0.078L284.7,86.89z M285.175,132.993l-0.906-45.251l-44.623-7.868l0.907,45.251 - L285.175,132.993"/> - </g> - <g> - <polygon fill="#7A88CC" points="217.142,142.917 216.216,96.736 238.745,79.251 239.671,125.432 "/> - </g> - <g> - <polygon fill="#7A88CC" points="216.216,96.736 216.207,96.27 238.736,78.786 238.745,79.251 "/> - </g> - <g> - <polygon fill="#7A88CC" points="217.116,97.358 239.646,79.874 240.553,125.125 218.023,142.609 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="218.023,142.609 217.116,97.358 239.646,79.874 240.553,125.125 "/> - </g> - <g> - <polygon fill="#7684CA" points="216.207,96.27 238.736,78.786 239.177,78.864 216.647,96.348 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.115,151.479 217.592,143.452 240.121,125.967 285.645,133.995 "/> - </g> - <g> - <polygon fill="#769AC7" points="262.646,150.476 218.023,142.609 240.553,125.125 285.175,132.993 "/> - </g> - <g> - <polygon fill="#7684CA" points="218.023,142.609 240.553,125.125 285.175,132.993 262.646,150.476 "/> - </g> - <g> - <polygon fill="#7684CA" points="263.564,151.558 263.115,151.479 285.645,133.995 286.094,134.074 "/> - </g> - <g> - <polygon fill="#7A88CC" points="263.556,151.101 286.085,133.617 286.094,134.074 263.564,151.558 "/> - </g> - <g> - <polygon fill="#7684CA" points="261.738,105.226 217.116,97.358 239.646,79.874 284.269,87.742 "/> - </g> - <g> - <polygon fill="#7A88CC" points="262.646,150.476 261.738,105.226 284.269,87.742 285.175,132.993 "/> - </g> - <g> - <polygon fill="#769AC7" points="217.116,97.358 239.646,79.874 284.269,87.742 261.738,105.226 "/> - </g> - <g> - <polygon fill="#628CBE" points="261.738,105.226 262.646,150.476 218.023,142.609 217.116,97.358 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="261.738,105.226 284.269,87.742 285.175,132.993 262.646,150.476 "/> - </g> - <g> - <polygon fill="#7684CA" points="216.647,96.348 239.177,78.864 284.7,86.89 262.171,104.375 "/> - </g> - <g> - <polygon fill="#7A88CC" points="262.63,104.919 285.159,87.435 286.085,133.617 263.556,151.101 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.171,104.375 284.7,86.89 285.149,86.97 262.62,104.455 "/> - </g> - <g> - <polygon fill="#7A88CC" points="262.62,104.455 285.149,86.97 285.159,87.435 262.63,104.919 "/> - </g> - <g> - <path fill="#6272C3" d="M262.171,104.375l0.449,0.08l0.01,0.465l0.926,46.182l0.009,0.457l-0.449-0.079l-45.523-8.027 - l-0.44-0.077l-0.01-0.458l-0.926-46.181l-0.009-0.466l0.44,0.078L262.171,104.375z M262.646,150.476l-0.907-45.25 - l-44.622-7.868l0.907,45.251L262.646,150.476"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M233.58,77.809l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L233.58,77.809z M234.055,123.903l-0.906-45.251 - l-44.622-7.867l0.907,45.25L234.055,123.903"/> - </g> - <g> - <polygon fill="#628CBE" points="233.148,78.652 234.055,123.903 189.434,116.035 188.526,70.785 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.03,134.285 166.021,133.828 188.551,116.343 188.561,116.8 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.48,134.364 166.03,134.285 188.561,116.8 189.01,116.879 "/> - </g> - <g> - <polygon fill="#7A88CC" points="166.021,133.828 165.096,87.646 187.625,70.162 188.551,116.343 "/> - </g> - <g> - <polygon fill="#7A88CC" points="165.096,87.646 165.087,87.189 187.616,69.705 187.625,70.162 "/> - </g> - <g> - <polygon fill="#7A88CC" points="165.997,88.269 188.526,70.785 189.434,116.035 166.904,133.519 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="166.904,133.519 165.997,88.269 188.526,70.785 189.434,116.035 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.087,87.189 187.616,69.705 188.065,69.784 165.536,87.268 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.995,142.389 166.48,134.364 189.01,116.879 234.524,124.905 "/> - </g> - <g> - <polygon fill="#7684CA" points="166.904,133.519 189.434,116.035 234.055,123.903 211.525,141.387 "/> - </g> - <g> - <polygon fill="#769AC7" points="211.525,141.387 166.904,133.519 189.434,116.035 234.055,123.903 "/> - </g> - <g> - <polygon fill="#7684CA" points="212.444,142.468 211.995,142.389 234.524,124.905 234.974,124.984 "/> - </g> - <g> - <polygon fill="#7A88CC" points="212.436,142.011 234.965,124.527 234.974,124.984 212.444,142.468 "/> - </g> - <g> - <polygon fill="#628CBE" points="210.618,96.136 211.525,141.387 166.904,133.519 165.997,88.269 "/> - </g> - <g> - <polygon fill="#7684CA" points="210.618,96.136 165.997,88.269 188.526,70.785 233.148,78.652 "/> - </g> - <g> - <polygon fill="#769AC7" points="165.997,88.269 188.526,70.785 233.148,78.652 210.618,96.136 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.525,141.387 210.618,96.136 233.148,78.652 234.055,123.903 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="210.618,96.136 233.148,78.652 234.055,123.903 211.525,141.387 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.536,87.268 188.065,69.784 233.58,77.809 211.051,95.293 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.51,95.83 234.039,78.345 234.965,124.527 212.436,142.011 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.051,95.293 233.58,77.809 234.03,77.888 211.5,95.373 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.5,95.373 234.03,77.888 234.039,78.345 211.51,95.83 "/> - </g> - <g> - <path fill="#6272C3" d="M211.051,95.293l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L211.051,95.293z M211.525,141.387l-0.907-45.251 - l-44.621-7.867l0.907,45.25L211.525,141.387"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="181.768,69.301 182.674,114.551 138.053,106.683 137.146,61.433 "/> - </g> - <g> - <polygon fill="#7A88CC" points="114.649,124.932 114.64,124.476 137.169,106.992 137.179,107.448 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.1,125.012 114.649,124.932 137.179,107.448 137.629,107.528 "/> - </g> - <g> - <polygon fill="#7684CA" points="160.613,133.038 115.1,125.012 137.629,107.528 183.143,115.553 "/> - </g> - <g> - <polygon fill="#7684CA" points="115.523,124.167 138.053,106.683 182.674,114.551 160.145,132.036 "/> - </g> - <g> - <polygon fill="#769AC7" points="160.145,132.036 115.523,124.167 138.053,106.683 182.674,114.551 "/> - </g> - <g> - <polygon fill="#6272C3" points="183.584,115.176 182.658,68.994 182.648,68.529 182.198,68.449 136.685,60.423 136.234,60.344 - 136.243,60.81 137.169,106.992 137.177,107.363 138.053,106.683 137.146,61.433 181.768,69.301 182.674,114.551 181.709,115.3 - 183.143,115.553 183.593,115.632 "/> - </g> - <g> - <polygon fill="#7A88CC" points="114.64,124.476 113.714,78.294 136.243,60.81 137.169,106.992 "/> - </g> - <g> - <polygon fill="#7A88CC" points="113.714,78.294 113.705,77.829 136.234,60.344 136.243,60.81 "/> - </g> - <g> - <polygon fill="#7A88CC" points="114.616,78.917 137.146,61.433 138.053,106.683 115.523,124.167 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="115.523,124.167 114.616,78.917 137.146,61.433 138.053,106.683 "/> - </g> - <g> - <polygon fill="#7684CA" points="113.705,77.829 136.234,60.344 136.685,60.423 114.155,77.908 "/> - </g> - <g> - <polygon fill="#7684CA" points="161.063,133.117 160.613,133.038 183.143,115.553 183.593,115.632 "/> - </g> - <g> - <polygon fill="#7A88CC" points="161.055,132.661 183.584,115.176 183.593,115.632 161.063,133.117 "/> - </g> - <g> - <polygon fill="#628CBE" points="159.237,86.786 160.145,132.036 115.523,124.167 114.616,78.917 "/> - </g> - <g> - <polygon fill="#7684CA" points="159.237,86.786 114.616,78.917 137.146,61.433 181.768,69.301 "/> - </g> - <g> - <polygon fill="#769AC7" points="114.616,78.917 137.146,61.433 181.768,69.301 159.237,86.786 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.145,132.036 159.237,86.786 181.768,69.301 182.674,114.551 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="159.237,86.786 181.768,69.301 182.674,114.551 160.145,132.036 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.155,77.908 136.685,60.423 182.198,68.449 159.669,85.933 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.129,86.478 182.658,68.994 183.584,115.176 161.055,132.661 "/> - </g> - <g> - <polygon fill="#7684CA" points="159.669,85.933 182.198,68.449 182.648,68.529 160.119,86.013 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.119,86.013 182.648,68.529 182.658,68.994 160.129,86.478 "/> - </g> - <g> - <path fill="#6272C3" d="M159.669,85.933l0.45,0.08l0.01,0.465l0.926,46.183l0.009,0.456l-0.45-0.079l-45.514-8.025l-0.45-0.08 - l-0.01-0.456l-0.926-46.182l-0.009-0.466l0.45,0.079L159.669,85.933z M160.145,132.036l-0.907-45.25l-44.621-7.868l0.907,45.25 - L160.145,132.036"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="130.386,8.948 131.293,54.199 86.67,46.331 85.763,1.08 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.268,64.58 63.259,64.123 85.788,46.638 85.797,47.096 "/> - </g> - <g> - <polygon fill="#7684CA" points="63.709,64.658 63.268,64.58 85.797,47.096 86.239,47.173 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.259,64.123 62.333,17.941 84.862,0.457 85.788,46.638 "/> - </g> - <g> - <polygon fill="#7A88CC" points="63.233,18.564 85.763,1.08 86.67,46.331 64.141,63.815 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="64.141,63.815 63.233,18.564 85.763,1.08 86.67,46.331 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.231,72.685 63.709,64.658 86.239,47.173 131.761,55.201 "/> - </g> - <g> - <polygon fill="#7684CA" points="64.141,63.815 86.67,46.331 131.293,54.199 108.764,71.683 "/> - </g> - <g> - <polygon fill="#769AC7" points="108.764,71.683 64.141,63.815 86.67,46.331 131.293,54.199 "/> - </g> - <g> - <polygon fill="#6272C3" points="132.193,54.821 131.268,8.64 131.259,8.183 130.816,8.104 85.295,0.078 84.853,0 84.862,0.457 - 84.888,1.759 85.763,1.08 130.386,8.948 131.293,54.199 130.327,54.948 131.761,55.201 132.203,55.279 "/> - </g> - <g> - <polygon fill="#7A88CC" points="62.333,17.941 62.323,17.484 84.853,0 84.862,0.457 "/> - </g> - <g> - <polygon fill="#7684CA" points="62.323,17.484 84.853,0 85.295,0.078 62.766,17.562 "/> - </g> - <g> - <polygon fill="#7684CA" points="109.673,72.763 109.231,72.685 131.761,55.201 132.203,55.279 "/> - </g> - <g> - <polygon fill="#7A88CC" points="109.664,72.305 132.193,54.821 132.203,55.279 109.673,72.763 "/> - </g> - <g> - <polygon fill="#628CBE" points="107.856,26.432 108.764,71.683 64.141,63.815 63.233,18.564 "/> - </g> - <g> - <polygon fill="#7684CA" points="107.856,26.432 63.233,18.564 85.763,1.08 130.386,8.948 "/> - </g> - <g> - <polygon fill="#769AC7" points="63.233,18.564 85.763,1.08 130.386,8.948 107.856,26.432 "/> - </g> - <g> - <polygon fill="#7A88CC" points="108.764,71.683 107.856,26.432 130.386,8.948 131.293,54.199 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="107.856,26.432 130.386,8.948 131.293,54.199 108.764,71.683 "/> - </g> - <g> - <polygon fill="#7684CA" points="62.766,17.562 85.295,0.078 130.816,8.104 108.287,25.589 "/> - </g> - <g> - <polygon fill="#7A88CC" points="108.738,26.124 131.268,8.64 132.193,54.821 109.664,72.305 "/> - </g> - <g> - <polygon fill="#7684CA" points="108.287,25.589 130.816,8.104 131.259,8.183 108.729,25.667 "/> - </g> - <g> - <polygon fill="#7A88CC" points="108.729,25.667 131.259,8.183 131.268,8.64 108.738,26.124 "/> - </g> - <g> - <path fill="#6272C3" d="M108.287,25.589l0.442,0.078l0.009,0.457l0.926,46.181l0.009,0.458l-0.441-0.078l-45.522-8.027 - l-0.441-0.078l-0.009-0.457l-0.926-46.181l-0.01-0.457l0.442,0.078L108.287,25.589z M108.764,71.683l-0.907-45.251 - l-44.623-7.868l0.907,45.251L108.764,71.683"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M283.7,35.896l0.441,0.078l0.01,0.457l0.926,46.181l0.009,0.457l-0.442-0.077l-45.521-8.027 - l-0.441-0.077l-0.009-0.458l-0.926-46.181l-0.01-0.458l0.441,0.078L283.7,35.896z M284.176,81.991l-0.907-45.251l-44.622-7.868 - l0.907,45.25L284.176,81.991"/> - </g> - <g> - <polygon fill="#628CBE" points="283.269,36.74 284.176,81.991 239.554,74.123 238.646,28.872 "/> - </g> - <g> - <polygon fill="#7A88CC" points="216.151,92.373 216.142,91.915 238.672,74.43 238.681,74.888 "/> - </g> - <g> - <polygon fill="#7684CA" points="216.593,92.45 216.151,92.373 238.681,74.888 239.122,74.965 "/> - </g> - <g> - <polygon fill="#7A88CC" points="216.142,91.915 215.216,45.734 237.746,28.25 238.672,74.43 "/> - </g> - <g> - <polygon fill="#7A88CC" points="215.216,45.734 215.207,45.277 237.736,27.792 237.746,28.25 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="217.024,91.607 216.117,46.356 238.646,28.872 239.554,74.123 "/> - </g> - <g> - <polygon fill="#7A88CC" points="216.117,46.356 238.646,28.872 239.554,74.123 217.024,91.607 "/> - </g> - <g> - <polygon fill="#7684CA" points="215.207,45.277 237.736,27.792 238.178,27.87 215.648,45.354 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.114,100.477 216.593,92.45 239.122,74.965 284.644,82.993 "/> - </g> - <g> - <polygon fill="#769AC7" points="261.646,99.475 217.024,91.607 239.554,74.123 284.176,81.991 "/> - </g> - <g> - <polygon fill="#7684CA" points="217.024,91.607 239.554,74.123 284.176,81.991 261.646,99.475 "/> - </g> - <g> - <polygon fill="#7684CA" points="262.557,100.554 262.114,100.477 284.644,82.993 285.086,83.07 "/> - </g> - <g> - <polygon fill="#7A88CC" points="262.547,100.097 285.077,82.613 285.086,83.07 262.557,100.554 "/> - </g> - <g> - <polygon fill="#628CBE" points="260.739,54.224 261.646,99.475 217.024,91.607 216.117,46.356 "/> - </g> - <g> - <polygon fill="#769AC7" points="216.117,46.356 238.646,28.872 283.269,36.74 260.739,54.224 "/> - </g> - <g> - <polygon fill="#7684CA" points="260.739,54.224 216.117,46.356 238.646,28.872 283.269,36.74 "/> - </g> - <g> - <polygon fill="#7A88CC" points="261.646,99.475 260.739,54.224 283.269,36.74 284.176,81.991 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="260.739,54.224 283.269,36.74 284.176,81.991 261.646,99.475 "/> - </g> - <g> - <polygon fill="#7684CA" points="215.648,45.354 238.178,27.87 283.7,35.896 261.17,53.381 "/> - </g> - <g> - <polygon fill="#7A88CC" points="261.621,53.917 284.151,36.432 285.077,82.613 262.547,100.097 "/> - </g> - <g> - <polygon fill="#7684CA" points="261.17,53.381 283.7,35.896 284.142,35.975 261.612,53.458 "/> - </g> - <g> - <polygon fill="#7A88CC" points="261.612,53.458 284.142,35.975 284.151,36.432 261.621,53.917 "/> - </g> - <g> - <path fill="#6272C3" d="M261.17,53.381l0.442,0.077l0.009,0.458l0.926,46.181l0.01,0.457l-0.442-0.077l-45.521-8.027 - l-0.441-0.077l-0.01-0.458l-0.926-46.181l-0.009-0.457l0.441,0.077L261.17,53.381z M261.646,99.475l-0.907-45.251 - l-44.622-7.868l0.907,45.251L261.646,99.475"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="232.149,27.65 233.057,72.91 188.434,65.041 187.526,19.782 "/> - </g> - <g> - <polygon fill="#7A88CC" points="165.031,83.291 165.021,82.826 187.552,65.341 187.561,65.806 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.481,83.371 165.031,83.291 187.561,65.806 188.011,65.886 "/> - </g> - <g> - <path fill="#6272C3" d="M232.58,26.807l0.441,0.078l0.01,0.465l0.925,46.173l0.01,0.465l-0.441-0.077l-45.514-8.025l-0.45-0.08 - l-0.009-0.465l-0.926-46.173l-0.01-0.465l0.45,0.079L232.58,26.807z M233.057,72.91l-0.907-45.259l-44.623-7.868l0.907,45.258 - L233.057,72.91"/> - </g> - <g> - <polygon fill="#7A88CC" points="165.021,82.826 164.097,36.652 186.626,19.168 187.552,65.341 "/> - </g> - <g> - <polygon fill="#7A88CC" points="164.097,36.652 164.087,36.187 186.616,18.703 186.626,19.168 "/> - </g> - <g> - <polygon fill="#7A88CC" points="164.997,37.267 187.526,19.782 188.434,65.041 165.904,82.525 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="165.904,82.525 164.997,37.267 187.526,19.782 188.434,65.041 "/> - </g> - <g> - <polygon fill="#7684CA" points="164.087,36.187 186.616,18.703 187.066,18.782 164.537,36.266 "/> - </g> - <g> - <polygon fill="#7684CA" points="210.995,91.396 165.481,83.371 188.011,65.886 233.524,73.912 "/> - </g> - <g> - <polygon fill="#769AC7" points="210.527,90.394 165.904,82.525 188.434,65.041 233.057,72.91 "/> - </g> - <g> - <polygon fill="#7684CA" points="165.904,82.525 188.434,65.041 233.057,72.91 210.527,90.394 "/> - </g> - <g> - <polygon fill="#7684CA" points="211.437,91.473 210.995,91.396 233.524,73.912 233.966,73.989 "/> - </g> - <g> - <polygon fill="#7A88CC" points="211.427,91.008 233.956,73.524 233.966,73.989 211.437,91.473 "/> - </g> - <g> - <polygon fill="#7A88CC" points="210.527,90.394 209.62,45.135 232.149,27.65 233.057,72.91 "/> - </g> - <g> - <polygon fill="#769AC7" points="164.997,37.267 187.526,19.782 232.149,27.65 209.62,45.135 "/> - </g> - <g> - <polygon fill="#7684CA" points="209.62,45.135 164.997,37.267 187.526,19.782 232.149,27.65 "/> - </g> - <g> - <polygon fill="#628CBE" points="209.62,45.135 210.527,90.394 165.904,82.525 164.997,37.267 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="209.62,45.135 232.149,27.65 233.057,72.91 210.527,90.394 "/> - </g> - <g> - <polygon fill="#7684CA" points="164.537,36.266 187.066,18.782 232.58,26.807 210.051,44.292 "/> - </g> - <g> - <polygon fill="#7A88CC" points="210.501,44.834 233.031,27.351 233.956,73.524 211.427,91.008 "/> - </g> - <g> - <polygon fill="#7684CA" points="210.051,44.292 232.58,26.807 233.021,26.885 210.492,44.37 "/> - </g> - <g> - <polygon fill="#7A88CC" points="210.492,44.37 233.021,26.885 233.031,27.351 210.501,44.834 "/> - </g> - <g> - <path fill="#6272C3" d="M210.051,44.292l0.441,0.078l0.009,0.465l0.926,46.174l0.01,0.465l-0.441-0.077l-45.514-8.025 - l-0.45-0.08l-0.01-0.465l-0.925-46.173l-0.01-0.465l0.45,0.079L210.051,44.292z M210.527,90.394l-0.907-45.259l-44.623-7.869 - l0.907,45.258L210.527,90.394"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M181.199,17.456l0.441,0.078l0.009,0.457l0.926,46.181l0.01,0.458l-0.441-0.078l-45.522-8.027 - l-0.441-0.077l-0.009-0.458l-0.926-46.181l-0.01-0.457l0.441,0.078L181.199,17.456z M181.675,63.549l-0.907-45.25 - l-44.623-7.868l0.907,45.25L181.675,63.549"/> - </g> - <g> - <polygon fill="#628CBE" points="180.768,18.299 181.675,63.549 137.052,55.681 136.145,10.431 "/> - </g> - <g> - <polygon fill="#7A88CC" points="113.65,73.931 113.641,73.473 136.171,55.989 136.18,56.447 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.091,74.008 113.65,73.931 136.18,56.447 136.621,56.524 "/> - </g> - <g> - <polygon fill="#7A88CC" points="113.641,73.473 112.715,27.292 135.245,9.808 136.171,55.989 "/> - </g> - <g> - <polygon fill="#7A88CC" points="112.715,27.292 112.706,26.835 135.235,9.351 135.245,9.808 "/> - </g> - <g> - <polygon fill="#7A88CC" points="113.615,27.915 136.145,10.431 137.052,55.681 114.522,73.166 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="114.522,73.166 113.615,27.915 136.145,10.431 137.052,55.681 "/> - </g> - <g> - <polygon fill="#7684CA" points="112.706,26.835 135.235,9.351 135.677,9.429 113.147,26.913 "/> - </g> - <g> - <polygon fill="#7684CA" points="159.614,82.036 114.091,74.008 136.621,56.524 182.144,64.551 "/> - </g> - <g> - <polygon fill="#769AC7" points="159.146,81.034 114.522,73.166 137.052,55.681 181.675,63.549 "/> - </g> - <g> - <polygon fill="#7684CA" points="114.522,73.166 137.052,55.681 181.675,63.549 159.146,81.034 "/> - </g> - <g> - <polygon fill="#7684CA" points="160.056,82.113 159.614,82.036 182.144,64.551 182.585,64.629 "/> - </g> - <g> - <polygon fill="#7A88CC" points="160.046,81.656 182.575,64.171 182.585,64.629 160.056,82.113 "/> - </g> - <g> - <polygon fill="#7684CA" points="158.238,35.783 113.615,27.915 136.145,10.431 180.768,18.299 "/> - </g> - <g> - <polygon fill="#769AC7" points="113.615,27.915 136.145,10.431 180.768,18.299 158.238,35.783 "/> - </g> - <g> - <polygon fill="#7A88CC" points="159.146,81.034 158.238,35.783 180.768,18.299 181.675,63.549 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="158.238,35.783 180.768,18.299 181.675,63.549 159.146,81.034 "/> - </g> - <g> - <path fill="#6272C3" d="M158.67,34.94l0.441,0.078l0.009,0.457l0.926,46.181l0.01,0.457l-0.441-0.077l-45.523-8.027 - l-0.44-0.077l-0.01-0.458l-0.926-46.181l-0.009-0.457l0.441,0.078L158.67,34.94z M159.146,81.034l-0.907-45.25l-44.623-7.868 - l0.907,45.25L159.146,81.034"/> - </g> - <g> - <polygon fill="#628CBE" points="113.615,27.915 114.522,73.166 159.146,81.034 158.238,35.783 "/> - </g> - <g> - <polygon fill="#7684CA" points="113.147,26.913 135.677,9.429 181.199,17.456 158.67,34.94 "/> - </g> - <g> - <polygon fill="#7A88CC" points="159.12,35.475 181.649,17.991 182.575,64.171 160.046,81.656 "/> - </g> - <g> - <polygon fill="#7684CA" points="158.67,34.94 181.199,17.456 181.641,17.534 159.111,35.018 "/> - </g> - <g> - <polygon fill="#7A88CC" points="159.111,35.018 181.641,17.534 181.649,17.991 159.12,35.475 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M101.874,188.302l0.449,0.08l0.01,0.465l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.458l-0.926-46.181l-0.009-0.466l0.449,0.079L101.874,188.302z M102.357,234.406l-0.907-45.259 - l-44.63-7.869l0.907,45.259L102.357,234.406"/> - </g> - <g> - <polygon fill="#628CBE" points="101.45,189.147 102.357,234.406 57.728,226.537 56.82,181.278 "/> - </g> - <g> - <polygon fill="#7A88CC" points="34.325,244.787 34.315,244.329 56.845,226.844 56.854,227.302 "/> - </g> - <g> - <polygon fill="#7684CA" points="34.774,244.866 34.325,244.787 56.854,227.302 57.304,227.381 "/> - </g> - <g> - <polygon fill="#7A88CC" points="34.315,244.329 33.39,198.148 55.919,180.664 56.845,226.844 "/> - </g> - <g> - <polygon fill="#7A88CC" points="33.39,198.148 33.381,197.682 55.91,180.198 55.919,180.664 "/> - </g> - <g> - <polygon fill="#7A88CC" points="34.291,198.762 56.82,181.278 57.728,226.537 35.198,244.021 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="35.198,244.021 34.291,198.762 56.82,181.278 57.728,226.537 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.381,197.682 55.91,180.198 56.359,180.277 33.83,197.761 "/> - </g> - <g> - <polygon fill="#7684CA" points="80.289,252.891 34.774,244.866 57.304,227.381 102.818,235.407 "/> - </g> - <g> - <polygon fill="#7684CA" points="35.198,244.021 57.728,226.537 102.357,234.406 79.828,251.89 "/> - </g> - <g> - <polygon fill="#769AC7" points="79.828,251.89 35.198,244.021 57.728,226.537 102.357,234.406 "/> - </g> - <g> - <polygon fill="#7684CA" points="80.738,252.97 80.289,252.891 102.818,235.407 103.268,235.486 "/> - </g> - <g> - <polygon fill="#7A88CC" points="80.729,252.513 103.259,235.029 103.268,235.486 80.738,252.97 "/> - </g> - <g> - <polygon fill="#7684CA" points="78.921,206.631 34.291,198.762 56.82,181.278 101.45,189.147 "/> - </g> - <g> - <polygon fill="#769AC7" points="34.291,198.762 56.82,181.278 101.45,189.147 78.921,206.631 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.828,251.89 78.921,206.631 101.45,189.147 102.357,234.406 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="78.921,206.631 101.45,189.147 102.357,234.406 79.828,251.89 "/> - </g> - <g> - <path fill="#6272C3" d="M79.345,205.787l0.449,0.079l0.01,0.466l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.458l-0.926-46.181l-0.009-0.466l0.449,0.079L79.345,205.787z M79.828,251.89l-0.907-45.259l-44.63-7.869 - l0.907,45.259L79.828,251.89"/> - </g> - <g> - <polygon fill="#628CBE" points="78.921,206.631 79.828,251.89 35.198,244.021 34.291,198.762 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.83,197.761 56.359,180.277 101.874,188.302 79.345,205.787 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.804,206.332 102.333,188.847 103.259,235.029 80.729,252.513 "/> - </g> - <g> - <polygon fill="#7684CA" points="79.345,205.787 101.874,188.302 102.323,188.382 79.794,205.866 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.794,205.866 102.323,188.382 102.333,188.847 79.804,206.332 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M254.757,216.103l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L254.757,216.103z M255.24,262.199l-0.907-45.251 - l-44.63-7.869l0.907,45.25L255.24,262.199"/> - </g> - <g> - <polygon fill="#628CBE" points="254.333,216.948 255.24,262.199 210.61,254.329 209.703,209.079 "/> - </g> - <g> - <polygon fill="#7A88CC" points="187.207,272.579 187.198,272.122 209.728,254.637 209.737,255.094 "/> - </g> - <g> - <polygon fill="#7684CA" points="187.657,272.658 187.207,272.579 209.737,255.094 210.187,255.173 "/> - </g> - <g> - <polygon fill="#7A88CC" points="187.198,272.122 186.272,225.94 208.802,208.456 209.728,254.637 "/> - </g> - <g> - <polygon fill="#7A88CC" points="186.272,225.94 186.264,225.483 208.793,207.999 208.802,208.456 "/> - </g> - <g> - <polygon fill="#7A88CC" points="187.174,226.562 209.703,209.079 210.61,254.329 188.081,271.813 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="188.081,271.813 187.174,226.562 209.703,209.079 210.61,254.329 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.264,225.483 208.793,207.999 209.242,208.078 186.713,225.562 "/> - </g> - <g> - <polygon fill="#7684CA" points="233.172,280.683 187.657,272.658 210.187,255.173 255.701,263.199 "/> - </g> - <g> - <polygon fill="#7684CA" points="188.081,271.813 210.61,254.329 255.24,262.199 232.711,279.682 "/> - </g> - <g> - <polygon fill="#769AC7" points="232.711,279.682 188.081,271.813 210.61,254.329 255.24,262.199 "/> - </g> - <g> - <polygon fill="#7684CA" points="233.621,280.762 233.172,280.683 255.701,263.199 256.15,263.278 "/> - </g> - <g> - <polygon fill="#7A88CC" points="233.612,280.305 256.142,262.821 256.15,263.278 233.621,280.762 "/> - </g> - <g> - <polygon fill="#628CBE" points="231.804,234.432 232.711,279.682 188.081,271.813 187.174,226.562 "/> - </g> - <g> - <polygon fill="#7684CA" points="231.804,234.432 187.174,226.562 209.703,209.079 254.333,216.948 "/> - </g> - <g> - <polygon fill="#769AC7" points="187.174,226.562 209.703,209.079 254.333,216.948 231.804,234.432 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.711,279.682 231.804,234.432 254.333,216.948 255.24,262.199 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="231.804,234.432 254.333,216.948 255.24,262.199 232.711,279.682 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.713,225.562 209.242,208.078 254.757,216.103 232.228,233.587 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.687,234.124 255.216,216.639 256.142,262.821 233.612,280.305 "/> - </g> - <g> - <polygon fill="#7684CA" points="232.228,233.587 254.757,216.103 255.207,216.182 232.677,233.667 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.677,233.667 255.207,216.182 255.216,216.639 232.687,234.124 "/> - </g> - <g> - <path fill="#6272C3" d="M232.228,233.587l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L232.228,233.587z M232.711,279.682l-0.907-45.25 - l-44.63-7.87l0.907,45.251L232.711,279.682"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="203.213,207.858 204.12,253.117 159.498,245.249 158.591,199.99 "/> - </g> - <g> - <polygon fill="#7A88CC" points="136.087,263.498 136.078,263.032 158.607,245.547 158.617,246.013 "/> - </g> - <g> - <polygon fill="#7684CA" points="136.537,263.577 136.087,263.498 158.617,246.013 159.066,246.092 "/> - </g> - <g> - <path fill="#6272C3" d="M203.645,207.015l0.441,0.078l0.01,0.465l0.926,46.173l0.009,0.466l-0.441-0.078l-45.522-8.026 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L203.645,207.015z M204.12,253.117l-0.907-45.259 - l-44.622-7.868l0.907,45.259L204.12,253.117"/> - </g> - <g> - <polygon fill="#7A88CC" points="136.078,263.032 135.152,216.859 157.682,199.375 158.607,245.547 "/> - </g> - <g> - <polygon fill="#7A88CC" points="135.152,216.859 135.144,216.393 157.673,198.909 157.682,199.375 "/> - </g> - <g> - <polygon fill="#7A88CC" points="136.062,217.474 158.591,199.99 159.498,245.249 136.969,262.733 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="136.969,262.733 136.062,217.474 158.591,199.99 159.498,245.249 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.144,216.393 157.673,198.909 158.122,198.988 135.593,216.472 "/> - </g> - <g> - <polygon fill="#7684CA" points="182.06,271.603 136.537,263.577 159.066,246.092 204.589,254.119 "/> - </g> - <g> - <polygon fill="#769AC7" points="181.591,270.601 136.969,262.733 159.498,245.249 204.12,253.117 "/> - </g> - <g> - <polygon fill="#7684CA" points="136.969,262.733 159.498,245.249 204.12,253.117 181.591,270.601 "/> - </g> - <g> - <polygon fill="#7684CA" points="182.501,271.681 182.06,271.603 204.589,254.119 205.03,254.197 "/> - </g> - <g> - <polygon fill="#7A88CC" points="182.492,271.215 205.021,253.731 205.03,254.197 182.501,271.681 "/> - </g> - <g> - <polygon fill="#628CBE" points="180.684,225.342 181.591,270.601 136.969,262.733 136.062,217.474 "/> - </g> - <g> - <polygon fill="#7684CA" points="180.684,225.342 136.062,217.474 158.591,199.99 203.213,207.858 "/> - </g> - <g> - <polygon fill="#769AC7" points="136.062,217.474 158.591,199.99 203.213,207.858 180.684,225.342 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.591,270.601 180.684,225.342 203.213,207.858 204.12,253.117 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="180.684,225.342 203.213,207.858 204.12,253.117 181.591,270.601 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.593,216.472 158.122,198.988 203.645,207.015 181.115,224.5 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.566,225.042 204.096,207.558 205.021,253.731 182.492,271.215 "/> - </g> - <g> - <polygon fill="#7684CA" points="181.115,224.5 203.645,207.015 204.086,207.093 181.557,224.578 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.557,224.578 204.086,207.093 204.096,207.558 181.566,225.042 "/> - </g> - <g> - <path fill="#6272C3" d="M181.115,224.5l0.441,0.078l0.01,0.465l0.926,46.173l0.009,0.466l-0.441-0.078l-45.522-8.026 - l-0.45-0.079l-0.009-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L181.115,224.5z M181.591,270.601l-0.907-45.259 - l-44.622-7.868l0.907,45.259L181.591,270.601"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M152.256,197.662l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L152.256,197.662z M152.739,243.757l-0.907-45.251 - l-44.63-7.869l0.907,45.25L152.739,243.757"/> - </g> - <g> - <polygon fill="#628CBE" points="151.832,198.506 152.739,243.757 108.109,235.887 107.202,190.637 "/> - </g> - <g> - <polygon fill="#7A88CC" points="84.706,254.137 84.697,253.68 107.227,236.196 107.236,236.653 "/> - </g> - <g> - <polygon fill="#7684CA" points="85.156,254.216 84.706,254.137 107.236,236.653 107.686,236.732 "/> - </g> - <g> - <polygon fill="#7A88CC" points="84.697,253.68 83.771,207.499 106.301,190.014 107.227,236.196 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.771,207.499 83.763,207.042 106.292,189.557 106.301,190.014 "/> - </g> - <g> - <polygon fill="#7A88CC" points="84.673,208.121 107.202,190.637 108.109,235.887 85.579,253.372 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="85.58,253.372 84.673,208.121 107.202,190.637 108.109,235.887 "/> - </g> - <g> - <polygon fill="#7684CA" points="83.763,207.042 106.292,189.557 106.741,189.636 84.212,207.121 "/> - </g> - <g> - <polygon fill="#7684CA" points="130.671,262.242 85.156,254.216 107.686,236.732 153.2,244.757 "/> - </g> - <g> - <polygon fill="#7684CA" points="85.579,253.372 108.109,235.887 152.739,243.757 130.21,261.242 "/> - </g> - <g> - <polygon fill="#769AC7" points="130.21,261.241 85.58,253.372 108.109,235.887 152.739,243.757 "/> - </g> - <g> - <polygon fill="#7684CA" points="131.12,262.321 130.671,262.242 153.2,244.757 153.649,244.836 "/> - </g> - <g> - <polygon fill="#7A88CC" points="131.111,261.864 153.641,244.379 153.649,244.836 131.12,262.321 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.303,215.991 84.673,208.121 107.202,190.637 151.832,198.506 "/> - </g> - <g> - <polygon fill="#7A88CC" points="130.21,261.242 129.303,215.991 151.832,198.506 152.739,243.757 "/> - </g> - <g> - <polygon fill="#769AC7" points="84.673,208.121 107.202,190.637 151.832,198.506 129.303,215.991 "/> - </g> - <g> - <polygon fill="#628CBE" points="129.303,215.991 130.21,261.241 85.58,253.372 84.673,208.121 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="129.303,215.991 151.832,198.506 152.739,243.757 130.21,261.241 "/> - </g> - <g> - <polygon fill="#7684CA" points="84.212,207.121 106.741,189.636 152.256,197.662 129.727,215.146 "/> - </g> - <g> - <polygon fill="#7A88CC" points="130.186,215.682 152.715,198.198 153.641,244.379 131.111,261.864 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.727,215.146 152.256,197.662 152.706,197.741 130.176,215.225 "/> - </g> - <g> - <polygon fill="#7A88CC" points="130.176,215.225 152.706,197.741 152.715,198.198 130.186,215.682 "/> - </g> - <g> - <path fill="#6272C3" d="M129.727,215.146l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L129.727,215.146z M130.21,261.242l-0.907-45.251 - l-44.63-7.87l0.906,45.251L130.21,261.242"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M100.875,137.308l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L100.875,137.308z M101.358,183.404l-0.907-45.251 - l-44.63-7.869l0.907,45.25L101.358,183.404"/> - </g> - <g> - <polygon fill="#628CBE" points="100.451,138.153 101.358,183.404 56.729,175.534 55.821,130.284 "/> - </g> - <g> - <polygon fill="#7A88CC" points="33.325,193.784 33.316,193.327 55.846,175.842 55.855,176.299 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.775,193.863 33.325,193.784 55.855,176.299 56.305,176.378 "/> - </g> - <g> - <polygon fill="#7A88CC" points="33.316,193.327 32.391,147.145 54.92,129.661 55.846,175.842 "/> - </g> - <g> - <polygon fill="#7A88CC" points="32.391,147.145 32.382,146.688 54.911,129.204 54.92,129.661 "/> - </g> - <g> - <polygon fill="#7A88CC" points="33.292,147.768 55.821,130.284 56.729,175.534 34.199,193.018 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="34.199,193.018 33.292,147.767 55.821,130.284 56.729,175.534 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.382,146.688 54.911,129.204 55.36,129.283 32.831,146.767 "/> - </g> - <g> - <polygon fill="#7684CA" points="79.29,201.888 33.775,193.863 56.305,176.378 101.819,184.404 "/> - </g> - <g> - <polygon fill="#7684CA" points="34.199,193.018 56.729,175.534 101.358,183.404 78.829,200.888 "/> - </g> - <g> - <polygon fill="#769AC7" points="78.829,200.887 34.199,193.018 56.729,175.534 101.358,183.404 "/> - </g> - <g> - <polygon fill="#7684CA" points="79.739,201.967 79.29,201.888 101.819,184.404 102.269,184.483 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.73,201.51 102.26,184.026 102.269,184.483 79.739,201.967 "/> - </g> - <g> - <polygon fill="#628CBE" points="77.922,155.637 78.829,200.887 34.199,193.018 33.292,147.767 "/> - </g> - <g> - <polygon fill="#7684CA" points="77.922,155.637 33.292,147.768 55.821,130.284 100.451,138.153 "/> - </g> - <g> - <polygon fill="#769AC7" points="33.292,147.767 55.821,130.284 100.451,138.153 77.922,155.637 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="77.922,155.637 100.451,138.153 101.358,183.404 78.829,200.887 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.829,200.888 77.922,155.637 100.451,138.153 101.358,183.404 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.831,146.767 55.36,129.283 100.875,137.308 78.346,154.792 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.805,155.329 101.334,137.844 102.26,184.026 79.73,201.51 "/> - </g> - <g> - <polygon fill="#7684CA" points="78.346,154.792 100.875,137.308 101.325,137.387 78.795,154.872 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.795,154.872 101.325,137.387 101.334,137.844 78.805,155.329 "/> - </g> - <g> - <path fill="#6272C3" d="M78.346,154.792l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L78.346,154.792z M78.829,200.888l-0.907-45.251 - l-44.63-7.869l0.907,45.25L78.829,200.888"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M253.758,165.1l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L253.758,165.1z M254.241,211.204l-0.907-45.259 - l-44.63-7.869l0.907,45.259L254.241,211.204"/> - </g> - <g> - <polygon fill="#628CBE" points="253.334,165.945 254.241,211.204 209.611,203.334 208.704,158.076 "/> - </g> - <g> - <polygon fill="#7A88CC" points="186.208,221.583 186.199,221.119 208.729,203.634 208.738,204.1 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.658,221.664 186.208,221.583 208.738,204.1 209.188,204.179 "/> - </g> - <g> - <polygon fill="#7A88CC" points="186.199,221.119 185.273,174.946 207.803,157.461 208.729,203.634 "/> - </g> - <g> - <polygon fill="#7A88CC" points="185.273,174.946 185.265,174.48 207.794,156.996 207.803,157.461 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="187.082,220.819 186.175,175.56 208.704,158.076 209.611,203.334 "/> - </g> - <g> - <polygon fill="#7A88CC" points="186.175,175.56 208.704,158.076 209.611,203.334 187.082,220.819 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.265,174.48 207.794,156.996 208.243,157.075 185.714,174.559 "/> - </g> - <g> - <polygon fill="#7684CA" points="232.173,229.689 186.658,221.664 209.188,204.179 254.702,212.205 "/> - </g> - <g> - <polygon fill="#769AC7" points="231.712,228.688 187.082,220.819 209.611,203.334 254.241,211.204 "/> - </g> - <g> - <polygon fill="#7684CA" points="187.082,220.819 209.611,203.334 254.241,211.204 231.712,228.688 "/> - </g> - <g> - <polygon fill="#7684CA" points="232.622,229.768 232.173,229.689 254.702,212.205 255.151,212.284 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.613,229.302 255.143,211.818 255.151,212.284 232.622,229.768 "/> - </g> - <g> - <polygon fill="#7684CA" points="230.805,183.429 186.175,175.56 208.704,158.076 253.334,165.945 "/> - </g> - <g> - <polygon fill="#769AC7" points="186.175,175.56 208.704,158.076 253.334,165.945 230.805,183.429 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.712,228.688 230.805,183.429 253.334,165.945 254.241,211.204 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="230.805,183.429 253.334,165.945 254.241,211.204 231.712,228.688 "/> - </g> - <g> - <path fill="#6272C3" d="M231.229,182.584l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.45-0.08l-0.009-0.465l-0.926-46.173l-0.009-0.466l0.449,0.079L231.229,182.584z M231.712,228.688l-0.907-45.259 - l-44.63-7.869l0.907,45.259L231.712,228.688"/> - </g> - <g> - <polygon fill="#628CBE" points="230.805,183.429 231.712,228.688 187.082,220.819 186.175,175.56 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.714,174.559 208.243,157.075 253.758,165.1 231.229,182.584 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.688,183.129 254.217,165.645 255.143,211.818 232.613,229.302 "/> - </g> - <g> - <polygon fill="#7684CA" points="231.229,182.584 253.758,165.1 254.207,165.179 231.678,182.664 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.678,182.664 254.207,165.179 254.217,165.645 231.688,183.129 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M202.638,156.01l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.08l-0.01-0.465l-0.926-46.173l-0.009-0.466l0.449,0.079L202.638,156.01z M203.121,202.114l-0.907-45.259 - l-44.63-7.869l0.907,45.259L203.121,202.114"/> - </g> - <g> - <polygon fill="#628CBE" points="202.214,156.855 203.121,202.114 158.491,194.245 157.584,148.986 "/> - </g> - <g> - <polygon fill="#7A88CC" points="135.089,212.494 135.079,212.029 157.608,194.544 157.618,195.009 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.538,212.574 135.089,212.494 157.618,195.009 158.067,195.089 "/> - </g> - <g> - <polygon fill="#7A88CC" points="135.079,212.029 134.153,165.856 156.683,148.372 157.608,194.544 "/> - </g> - <g> - <polygon fill="#7A88CC" points="134.153,165.856 134.145,165.39 156.674,147.906 156.683,148.372 "/> - </g> - <g> - <polygon fill="#7A88CC" points="135.055,166.47 157.584,148.986 158.491,194.245 135.962,211.729 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="135.962,211.729 135.055,166.47 157.584,148.986 158.491,194.245 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.145,165.39 156.674,147.906 157.123,147.985 134.594,165.469 "/> - </g> - <g> - <polygon fill="#7684CA" points="181.053,220.599 135.538,212.574 158.067,195.089 203.582,203.115 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.962,211.729 158.491,194.245 203.121,202.114 180.592,219.598 "/> - </g> - <g> - <polygon fill="#769AC7" points="180.592,219.598 135.962,211.729 158.491,194.245 203.121,202.114 "/> - </g> - <g> - <polygon fill="#7684CA" points="181.502,220.678 181.053,220.599 203.582,203.115 204.031,203.194 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.493,220.212 204.022,202.728 204.031,203.194 181.502,220.678 "/> - </g> - <g> - <polygon fill="#628CBE" points="179.685,174.339 180.592,219.598 135.962,211.729 135.055,166.47 "/> - </g> - <g> - <polygon fill="#7684CA" points="179.685,174.339 135.055,166.47 157.584,148.986 202.214,156.855 "/> - </g> - <g> - <polygon fill="#769AC7" points="135.055,166.47 157.584,148.986 202.214,156.855 179.685,174.339 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.592,219.598 179.685,174.339 202.214,156.855 203.121,202.114 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="179.685,174.339 202.214,156.855 203.121,202.114 180.592,219.598 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.594,165.469 157.123,147.985 202.638,156.01 180.108,173.495 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.567,174.04 203.097,156.555 204.022,202.728 181.493,220.212 "/> - </g> - <g> - <polygon fill="#7684CA" points="180.108,173.495 202.638,156.01 203.087,156.089 180.558,173.574 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.558,173.574 203.087,156.089 203.097,156.555 180.567,174.04 "/> - </g> - <g> - <path fill="#6272C3" d="M180.108,173.495l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.08l-0.01-0.465l-0.926-46.173l-0.009-0.466l0.449,0.079L180.108,173.495z M180.592,219.598l-0.907-45.259 - l-44.63-7.869l0.907,45.259L180.592,219.598"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M151.257,146.659l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L151.257,146.659z M151.74,192.762l-0.907-45.259 - l-44.63-7.869l0.907,45.259L151.74,192.762"/> - </g> - <g> - <polygon fill="#628CBE" points="150.833,147.503 151.74,192.762 107.11,184.893 106.203,139.634 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.708,203.142 83.698,202.677 106.228,185.193 106.237,185.659 "/> - </g> - <g> - <polygon fill="#7684CA" points="84.157,203.222 83.708,203.142 106.237,185.659 106.687,185.738 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.698,202.677 82.772,156.504 105.302,139.02 106.228,185.193 "/> - </g> - <g> - <polygon fill="#7A88CC" points="82.772,156.504 82.764,156.039 105.293,138.554 105.302,139.02 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.674,157.119 106.203,139.634 107.11,184.893 84.581,202.377 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="84.581,202.377 83.674,157.119 106.203,139.634 107.11,184.893 "/> - </g> - <g> - <polygon fill="#7684CA" points="82.764,156.039 105.293,138.554 105.742,138.633 83.213,156.118 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.672,211.248 84.157,203.222 106.687,185.738 152.201,193.763 "/> - </g> - <g> - <polygon fill="#769AC7" points="129.211,210.247 84.581,202.377 107.11,184.893 151.74,192.762 "/> - </g> - <g> - <polygon fill="#7684CA" points="84.581,202.377 107.11,184.893 151.74,192.762 129.211,210.247 "/> - </g> - <g> - <polygon fill="#7684CA" points="130.121,211.327 129.672,211.248 152.201,193.763 152.65,193.842 "/> - </g> - <g> - <polygon fill="#7A88CC" points="130.112,210.861 152.642,193.376 152.65,193.842 130.121,211.327 "/> - </g> - <g> - <polygon fill="#628CBE" points="128.304,164.988 129.211,210.247 84.581,202.377 83.674,157.119 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.304,164.988 83.674,157.119 106.203,139.634 150.833,147.503 "/> - </g> - <g> - <polygon fill="#769AC7" points="83.674,157.119 106.203,139.634 150.833,147.503 128.304,164.988 "/> - </g> - <g> - <polygon fill="#7A88CC" points="129.211,210.247 128.304,164.988 150.833,147.503 151.74,192.762 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="128.304,164.988 150.833,147.503 151.74,192.762 129.211,210.247 "/> - </g> - <g> - <polygon fill="#7684CA" points="83.213,156.118 105.742,138.633 151.257,146.659 128.728,164.143 "/> - </g> - <g> - <polygon fill="#7A88CC" points="129.187,164.688 151.716,147.204 152.642,193.376 130.112,210.861 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.728,164.143 151.257,146.659 151.706,146.739 129.177,164.222 "/> - </g> - <g> - <polygon fill="#7A88CC" points="129.177,164.222 151.706,146.739 151.716,147.204 129.187,164.688 "/> - </g> - <g> - <path fill="#6272C3" d="M128.728,164.143l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.08l-0.01-0.465l-0.926-46.173l-0.009-0.466l0.449,0.079L128.728,164.143z M129.211,210.247l-0.907-45.259 - l-44.63-7.869l0.907,45.259L129.211,210.247"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="100.038,86.979 100.944,132.229 56.314,124.36 55.407,79.109 "/> - </g> - <g> - <polygon fill="#7A88CC" points="32.911,142.609 32.902,142.152 55.432,124.667 55.441,125.125 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.361,142.689 32.911,142.609 55.441,125.125 55.891,125.205 "/> - </g> - <g> - <polygon fill="#7684CA" points="78.876,150.714 33.361,142.689 55.891,125.205 101.405,133.23 "/> - </g> - <g> - <polygon fill="#7684CA" points="33.785,141.844 56.314,124.36 100.944,132.229 78.415,149.713 "/> - </g> - <g> - <polygon fill="#769AC7" points="78.415,149.713 33.785,141.844 56.314,124.36 100.944,132.229 "/> - </g> - <g> - <polygon fill="#6272C3" points="101.846,132.852 100.92,86.67 100.91,86.205 100.461,86.125 54.946,78.1 54.497,78.021 - 54.506,78.487 55.432,124.667 55.439,125.039 56.314,124.36 55.407,79.109 100.038,86.979 100.944,132.229 99.979,132.978 - 101.405,133.23 101.854,133.309 "/> - </g> - <g> - <polygon fill="#7A88CC" points="32.902,142.152 31.977,95.971 54.506,78.487 55.432,124.667 "/> - </g> - <g> - <polygon fill="#7A88CC" points="31.977,95.971 31.968,95.505 54.497,78.021 54.506,78.487 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="33.785,141.844 32.878,96.593 55.407,79.109 56.314,124.36 "/> - </g> - <g> - <polygon fill="#7A88CC" points="32.878,96.593 55.407,79.109 56.314,124.36 33.785,141.844 "/> - </g> - <g> - <polygon fill="#7684CA" points="31.968,95.505 54.497,78.021 54.946,78.1 32.417,95.584 "/> - </g> - <g> - <polygon fill="#7684CA" points="79.325,150.793 78.876,150.714 101.405,133.23 101.854,133.309 "/> - </g> - <g> - <polygon fill="#7A88CC" points="79.316,150.336 101.846,132.852 101.854,133.309 79.325,150.793 "/> - </g> - <g> - <polygon fill="#628CBE" points="77.508,104.463 78.415,149.713 33.785,141.844 32.878,96.593 "/> - </g> - <g> - <polygon fill="#7684CA" points="77.508,104.463 32.878,96.593 55.407,79.109 100.038,86.979 "/> - </g> - <g> - <polygon fill="#769AC7" points="32.878,96.593 55.407,79.109 100.038,86.979 77.508,104.463 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="77.508,104.463 100.038,86.979 100.944,132.229 78.415,149.713 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.415,149.713 77.508,104.463 100.038,86.979 100.944,132.229 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.417,95.584 54.946,78.1 100.461,86.125 77.932,103.61 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.391,104.155 100.92,86.67 101.846,132.852 79.316,150.336 "/> - </g> - <g> - <polygon fill="#7684CA" points="77.932,103.61 100.461,86.125 100.91,86.205 78.381,103.689 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.381,103.689 100.91,86.205 100.92,86.67 78.391,104.155 "/> - </g> - <g> - <path fill="#6272C3" d="M77.932,103.61l0.449,0.079l0.01,0.466l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.08l-0.009-0.457l-0.926-46.181l-0.009-0.466l0.449,0.079L77.932,103.61z M78.415,149.713l-0.907-45.25l-44.63-7.87 - l0.907,45.251L78.415,149.713"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M253.344,113.926l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L253.344,113.926z M253.827,160.021l-0.907-45.25 - l-44.63-7.869l0.907,45.25L253.827,160.021"/> - </g> - <g> - <polygon fill="#628CBE" points="252.92,114.771 253.827,160.021 209.197,152.152 208.29,106.902 "/> - </g> - <g> - <polygon fill="#7A88CC" points="185.794,170.402 185.785,169.945 208.314,152.46 208.324,152.917 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.244,170.481 185.794,170.402 208.324,152.917 208.773,152.997 "/> - </g> - <g> - <polygon fill="#7A88CC" points="185.785,169.945 184.859,123.763 207.389,106.279 208.314,152.46 "/> - </g> - <g> - <polygon fill="#7A88CC" points="184.859,123.763 184.851,123.306 207.38,105.822 207.389,106.279 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="186.668,169.636 185.761,124.386 208.29,106.902 209.197,152.152 "/> - </g> - <g> - <polygon fill="#7A88CC" points="185.761,124.386 208.29,106.902 209.197,152.152 186.668,169.636 "/> - </g> - <g> - <polygon fill="#7684CA" points="184.851,123.306 207.38,105.822 207.829,105.901 185.3,123.385 "/> - </g> - <g> - <polygon fill="#7684CA" points="231.759,178.506 186.244,170.481 208.773,152.997 254.288,161.022 "/> - </g> - <g> - <polygon fill="#769AC7" points="231.298,177.505 186.668,169.636 209.197,152.152 253.827,160.021 "/> - </g> - <g> - <polygon fill="#7684CA" points="186.668,169.636 209.197,152.152 253.827,160.021 231.298,177.505 "/> - </g> - <g> - <polygon fill="#7684CA" points="232.208,178.585 231.759,178.506 254.288,161.022 254.737,161.101 "/> - </g> - <g> - <polygon fill="#7A88CC" points="232.199,178.128 254.729,160.644 254.737,161.101 232.208,178.585 "/> - </g> - <g> - <polygon fill="#628CBE" points="230.391,132.255 231.298,177.505 186.668,169.636 185.761,124.386 "/> - </g> - <g> - <polygon fill="#7684CA" points="230.391,132.255 185.761,124.386 208.29,106.902 252.92,114.771 "/> - </g> - <g> - <polygon fill="#769AC7" points="185.761,124.386 208.29,106.902 252.92,114.771 230.391,132.255 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.298,177.505 230.391,132.255 252.92,114.771 253.827,160.021 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="230.391,132.255 252.92,114.771 253.827,160.021 231.298,177.505 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.3,123.385 207.829,105.901 253.344,113.926 230.814,131.411 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.273,131.947 253.803,114.462 254.729,160.644 232.199,178.128 "/> - </g> - <g> - <polygon fill="#7684CA" points="230.814,131.411 253.344,113.926 253.794,114.005 231.264,131.49 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.264,131.49 253.794,114.005 253.803,114.462 231.273,131.947 "/> - </g> - <g> - <path fill="#6272C3" d="M230.814,131.411l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L230.814,131.411z M231.298,177.505l-0.907-45.25 - l-44.63-7.869l0.907,45.25L231.298,177.505"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M202.224,104.836l0.45,0.079l0.009,0.457l0.926,46.182l0.01,0.465l-0.45-0.079l-45.514-8.025 - l-0.442-0.078l-0.009-0.465l-0.926-46.182l-0.01-0.457l0.442,0.078L202.224,104.836z M202.708,150.939l-0.907-45.258 - l-44.623-7.868l0.907,45.258L202.708,150.939"/> - </g> - <g> - <polygon fill="#628CBE" points="201.801,105.681 202.708,150.939 158.085,143.071 157.178,97.813 "/> - </g> - <g> - <polygon fill="#7A88CC" points="134.683,161.321 134.673,160.856 157.203,143.372 157.212,143.836 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.124,161.399 134.683,161.321 157.212,143.836 157.654,143.915 "/> - </g> - <g> - <polygon fill="#7A88CC" points="134.673,160.856 133.747,114.674 156.277,97.19 157.203,143.372 "/> - </g> - <g> - <polygon fill="#7A88CC" points="133.747,114.674 133.738,114.217 156.268,96.733 156.277,97.19 "/> - </g> - <g> - <polygon fill="#7A88CC" points="134.648,115.297 157.178,97.813 158.085,143.071 135.556,160.555 "/> - </g> - <g> - <polygon fill="#7684CA" points="180.639,169.424 135.124,161.399 157.654,143.915 203.168,151.94 "/> - </g> - <g> - <polygon fill="#7684CA" points="135.556,160.555 158.085,143.071 202.708,150.939 180.179,168.423 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="135.556,160.555 134.648,115.297 157.178,97.813 158.085,143.071 "/> - </g> - <g> - <polygon fill="#7684CA" points="133.738,114.217 156.268,96.733 156.71,96.811 134.181,114.295 "/> - </g> - <g> - <polygon fill="#769AC7" points="180.179,168.423 135.556,160.555 158.085,143.071 202.708,150.939 "/> - </g> - <g> - <polygon fill="#7684CA" points="181.089,169.503 180.639,169.424 203.168,151.94 203.618,152.019 "/> - </g> - <g> - <polygon fill="#7A88CC" points="181.079,169.039 203.608,151.554 203.618,152.019 181.089,169.503 "/> - </g> - <g> - <polygon fill="#628CBE" points="179.271,123.166 180.179,168.423 135.556,160.555 134.648,115.297 "/> - </g> - <g> - <polygon fill="#769AC7" points="134.648,115.297 157.178,97.813 201.801,105.681 179.271,123.166 "/> - </g> - <g> - <polygon fill="#7684CA" points="179.271,123.166 134.648,115.297 157.178,97.813 201.801,105.681 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.179,168.423 179.271,123.166 201.801,105.681 202.708,150.939 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="179.271,123.166 201.801,105.681 202.708,150.939 180.179,168.423 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.181,114.295 156.71,96.811 202.224,104.836 179.694,122.321 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.153,122.857 202.683,105.373 203.608,151.554 181.079,169.039 "/> - </g> - <g> - <polygon fill="#7684CA" points="179.694,122.321 202.224,104.836 202.674,104.916 180.145,122.4 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.145,122.4 202.674,104.916 202.683,105.373 180.153,122.857 "/> - </g> - <g> - <path fill="#6272C3" d="M179.694,122.321l0.45,0.079l0.009,0.457l0.926,46.182l0.01,0.465l-0.45-0.079l-45.515-8.025 - l-0.441-0.078l-0.01-0.465l-0.926-46.182l-0.009-0.457l0.442,0.078L179.694,122.321z M180.179,168.423l-0.907-45.258 - l-44.623-7.868l0.907,45.258L180.179,168.423"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M150.843,95.485l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L150.843,95.485z M151.326,141.58l-0.907-45.25l-44.63-7.869 - l0.907,45.25L151.326,141.58"/> - </g> - <g> - <polygon fill="#628CBE" points="150.419,96.33 151.326,141.58 106.696,133.71 105.789,88.46 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.293,151.96 83.284,151.503 105.813,134.019 105.823,134.476 "/> - </g> - <g> - <polygon fill="#7684CA" points="83.743,152.04 83.293,151.96 105.823,134.476 106.272,134.555 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.284,151.503 82.358,105.322 104.888,87.837 105.813,134.019 "/> - </g> - <g> - <polygon fill="#7A88CC" points="82.358,105.322 82.35,104.865 104.879,87.38 104.888,87.837 "/> - </g> - <g> - <polygon fill="#7A88CC" points="83.26,105.945 105.789,88.46 106.696,133.71 84.167,151.195 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="84.167,151.195 83.26,105.945 105.789,88.46 106.696,133.71 "/> - </g> - <g> - <polygon fill="#7684CA" points="82.35,104.865 104.879,87.38 105.328,87.459 82.799,104.944 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.258,160.065 83.743,152.04 106.272,134.555 151.787,142.581 "/> - </g> - <g> - <polygon fill="#769AC7" points="128.797,159.064 84.167,151.195 106.696,133.71 151.326,141.58 "/> - </g> - <g> - <polygon fill="#7684CA" points="84.167,151.195 106.696,133.71 151.326,141.58 128.797,159.064 "/> - </g> - <g> - <polygon fill="#7684CA" points="129.707,160.144 129.258,160.065 151.787,142.581 152.236,142.66 "/> - </g> - <g> - <polygon fill="#7A88CC" points="129.698,159.687 152.228,142.203 152.236,142.66 129.707,160.144 "/> - </g> - <g> - <polygon fill="#628CBE" points="127.89,113.814 128.797,159.064 84.167,151.195 83.26,105.945 "/> - </g> - <g> - <polygon fill="#7684CA" points="127.89,113.814 83.26,105.945 105.789,88.46 150.419,96.33 "/> - </g> - <g> - <polygon fill="#769AC7" points="83.26,105.945 105.789,88.46 150.419,96.33 127.89,113.814 "/> - </g> - <g> - <polygon fill="#7A88CC" points="128.797,159.064 127.89,113.814 150.419,96.33 151.326,141.58 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="127.89,113.814 150.419,96.33 151.326,141.58 128.797,159.064 "/> - </g> - <g> - <polygon fill="#7684CA" points="82.799,104.944 105.328,87.459 150.843,95.485 128.313,112.969 "/> - </g> - <g> - <polygon fill="#7A88CC" points="128.772,113.505 151.302,96.021 152.228,142.203 129.698,159.687 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.313,112.969 150.843,95.485 151.293,95.564 128.763,113.048 "/> - </g> - <g> - <polygon fill="#7A88CC" points="128.763,113.048 151.293,95.564 151.302,96.021 128.772,113.505 "/> - </g> - <g> - <path fill="#6272C3" d="M128.313,112.969l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L128.313,112.969z M128.797,159.064l-0.907-45.25 - l-44.63-7.869l0.907,45.25L128.797,159.064"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="99.038,35.976 99.945,81.227 55.315,73.357 54.408,28.106 "/> - </g> - <g> - <polygon fill="#7A88CC" points="31.913,91.615 31.903,91.15 54.433,73.666 54.442,74.131 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.362,91.695 31.913,91.615 54.442,74.131 54.892,74.21 "/> - </g> - <g> - <path fill="#6272C3" d="M99.462,35.132l0.449,0.079l0.01,0.457l0.926,46.181l0.009,0.466l-0.449-0.079L54.892,74.21 - l-0.449-0.079l-0.01-0.466l-0.926-46.182l-0.009-0.457l0.449,0.08L99.462,35.132z M99.945,81.227l-0.907-45.251l-44.63-7.87 - l0.907,45.25L99.945,81.227"/> - </g> - <g> - <polygon fill="#7A88CC" points="31.903,91.15 30.978,44.968 53.507,27.484 54.433,73.666 "/> - </g> - <g> - <polygon fill="#7A88CC" points="30.978,44.968 30.969,44.511 53.498,27.027 53.507,27.484 "/> - </g> - <g> - <polygon fill="#7A88CC" points="31.879,45.59 54.408,28.106 55.315,73.357 32.786,90.841 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="32.786,90.841 31.879,45.59 54.408,28.106 55.315,73.357 "/> - </g> - <g> - <polygon fill="#7684CA" points="30.969,44.511 53.498,27.027 53.947,27.106 31.418,44.59 "/> - </g> - <g> - <polygon fill="#7684CA" points="77.877,99.72 32.362,91.695 54.892,74.21 100.406,82.236 "/> - </g> - <g> - <polygon fill="#769AC7" points="77.416,98.711 32.786,90.841 55.315,73.357 99.945,81.227 "/> - </g> - <g> - <polygon fill="#7684CA" points="32.786,90.841 55.315,73.357 99.945,81.227 77.416,98.711 "/> - </g> - <g> - <polygon fill="#7684CA" points="78.326,99.799 77.877,99.72 100.406,82.236 100.855,82.315 "/> - </g> - <g> - <polygon fill="#7A88CC" points="78.317,99.333 100.847,81.849 100.855,82.315 78.326,99.799 "/> - </g> - <g> - <polygon fill="#628CBE" points="76.509,53.46 77.416,98.711 32.786,90.841 31.879,45.59 "/> - </g> - <g> - <polygon fill="#7684CA" points="76.509,53.46 31.879,45.59 54.408,28.106 99.038,35.976 "/> - </g> - <g> - <polygon fill="#769AC7" points="31.879,45.59 54.408,28.106 99.038,35.976 76.509,53.46 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="76.509,53.46 99.038,35.976 99.945,81.227 77.416,98.711 "/> - </g> - <g> - <polygon fill="#7A88CC" points="77.416,98.711 76.509,53.46 99.038,35.976 99.945,81.227 "/> - </g> - <g> - <polygon fill="#7684CA" points="31.418,44.59 53.947,27.106 99.462,35.132 76.933,52.616 "/> - </g> - <g> - <polygon fill="#7A88CC" points="77.392,53.152 99.921,35.668 100.847,81.849 78.317,99.333 "/> - </g> - <g> - <polygon fill="#7684CA" points="76.933,52.616 99.462,35.132 99.911,35.211 77.382,52.695 "/> - </g> - <g> - <polygon fill="#7A88CC" points="77.382,52.695 99.911,35.211 99.921,35.668 77.392,53.152 "/> - </g> - <g> - <path fill="#6272C3" d="M76.933,52.616l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.08l-0.01-0.465l-0.926-46.182l-0.009-0.457l0.449,0.079L76.933,52.616z M77.416,98.711L76.509,53.46l-44.63-7.87 - l0.907,45.251L77.416,98.711"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="251.921,63.768 252.828,109.027 208.198,101.158 207.291,55.899 "/> - </g> - <g> - <polygon fill="#7A88CC" points="184.796,119.408 184.786,118.942 207.315,101.458 207.325,101.923 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.245,119.487 184.796,119.408 207.325,101.923 207.774,102.002 "/> - </g> - <g> - <path fill="#6272C3" d="M252.345,62.923l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L252.345,62.923z M252.828,109.027l-0.907-45.259 - l-44.63-7.869l0.907,45.259L252.828,109.027"/> - </g> - <g> - <polygon fill="#7A88CC" points="184.786,118.942 183.86,72.769 206.39,55.285 207.315,101.458 "/> - </g> - <g> - <polygon fill="#7A88CC" points="183.86,72.769 183.852,72.303 206.381,54.819 206.39,55.285 "/> - </g> - <g> - <polygon fill="#7A88CC" points="184.762,73.383 207.291,55.899 208.198,101.158 185.669,118.642 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="185.669,118.642 184.762,73.383 207.291,55.899 208.198,101.158 "/> - </g> - <g> - <polygon fill="#7684CA" points="183.852,72.303 206.381,54.819 206.83,54.898 184.301,72.382 "/> - </g> - <g> - <polygon fill="#7684CA" points="230.76,127.512 185.245,119.487 207.774,102.002 253.289,110.028 "/> - </g> - <g> - <polygon fill="#7684CA" points="185.669,118.642 208.198,101.158 252.828,109.027 230.299,126.511 "/> - </g> - <g> - <polygon fill="#769AC7" points="230.299,126.511 185.669,118.642 208.198,101.158 252.828,109.027 "/> - </g> - <g> - <polygon fill="#7684CA" points="231.209,127.591 230.76,127.512 253.289,110.028 253.738,110.107 "/> - </g> - <g> - <polygon fill="#7A88CC" points="231.2,127.125 253.729,109.641 253.738,110.107 231.209,127.591 "/> - </g> - <g> - <polygon fill="#7684CA" points="229.392,81.252 184.762,73.383 207.291,55.899 251.921,63.768 "/> - </g> - <g> - <polygon fill="#769AC7" points="184.762,73.383 207.291,55.899 251.921,63.768 229.392,81.252 "/> - </g> - <g> - <polygon fill="#7A88CC" points="230.299,126.511 229.392,81.252 251.921,63.768 252.828,109.027 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="229.392,81.252 251.921,63.768 252.828,109.027 230.299,126.511 "/> - </g> - <g> - <path fill="#6272C3" d="M229.815,80.408l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L229.815,80.408z M230.299,126.511l-0.907-45.259 - l-44.63-7.869l0.907,45.259L230.299,126.511"/> - </g> - <g> - <path fill="#628CBE" d="M229.392,81.252l-44.63-7.869l0.907,45.259l44.63,7.869L229.392,81.252z M184.762,73.383 - L184.762,73.383L184.762,73.383L184.762,73.383z"/> - </g> - <g> - <polygon fill="#7684CA" points="184.301,72.382 206.83,54.898 252.345,62.923 229.815,80.408 "/> - </g> - <g> - <polygon fill="#7A88CC" points="230.274,80.953 252.804,63.468 253.729,109.641 231.2,127.125 "/> - </g> - <g> - <polygon fill="#7684CA" points="229.815,80.408 252.345,62.923 252.794,63.003 230.265,80.487 "/> - </g> - <g> - <polygon fill="#7A88CC" points="230.265,80.487 252.794,63.003 252.804,63.468 230.274,80.953 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M201.225,53.833l0.45,0.08l0.009,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L201.225,53.833z M201.708,99.937l-0.907-45.259 - l-44.622-7.868l0.907,45.259L201.708,99.937"/> - </g> - <g> - <polygon fill="#628CBE" points="200.801,54.678 201.708,99.937 157.086,92.069 156.179,46.81 "/> - </g> - <g> - <polygon fill="#7A88CC" points="133.676,110.318 133.666,109.852 156.195,92.368 156.205,92.833 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.125,110.397 133.676,110.318 156.205,92.833 156.654,92.913 "/> - </g> - <g> - <polygon fill="#7A88CC" points="133.666,109.852 132.74,63.679 155.27,46.195 156.195,92.368 "/> - </g> - <g> - <polygon fill="#7A88CC" points="132.74,63.679 132.731,63.213 155.261,45.729 155.27,46.195 "/> - </g> - <g> - <polygon fill="#7A88CC" points="133.649,64.294 156.179,46.81 157.086,92.069 134.557,109.553 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="134.557,109.553 133.649,64.294 156.179,46.81 157.086,92.069 "/> - </g> - <g> - <polygon fill="#7684CA" points="132.731,63.213 155.261,45.729 155.71,45.808 133.181,63.292 "/> - </g> - <g> - <polygon fill="#7684CA" points="179.64,118.422 134.125,110.397 156.654,92.913 202.169,100.938 "/> - </g> - <g> - <polygon fill="#7684CA" points="134.557,109.553 157.086,92.069 201.708,99.937 179.179,117.421 "/> - </g> - <g> - <polygon fill="#769AC7" points="179.179,117.421 134.557,109.553 157.086,92.069 201.708,99.937 "/> - </g> - <g> - <polygon fill="#7684CA" points="180.089,118.501 179.64,118.422 202.169,100.938 202.618,101.017 "/> - </g> - <g> - <polygon fill="#7A88CC" points="180.08,118.036 202.609,100.551 202.618,101.017 180.089,118.501 "/> - </g> - <g> - <polygon fill="#628CBE" points="178.271,72.163 179.179,117.421 134.557,109.553 133.649,64.294 "/> - </g> - <g> - <polygon fill="#7684CA" points="178.271,72.163 133.649,64.294 156.179,46.81 200.801,54.678 "/> - </g> - <g> - <polygon fill="#769AC7" points="133.649,64.294 156.179,46.81 200.801,54.678 178.271,72.163 "/> - </g> - <g> - <polygon fill="#7A88CC" points="179.179,117.421 178.271,72.163 200.801,54.678 201.708,99.937 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="178.271,72.163 200.801,54.678 201.708,99.937 179.179,117.421 "/> - </g> - <g> - <polygon fill="#7684CA" points="133.181,63.292 155.71,45.808 201.225,53.833 178.695,71.318 "/> - </g> - <g> - <polygon fill="#7A88CC" points="179.154,71.863 201.684,54.378 202.609,100.551 180.08,118.036 "/> - </g> - <g> - <polygon fill="#7684CA" points="178.695,71.318 201.225,53.833 201.675,53.914 179.145,71.398 "/> - </g> - <g> - <polygon fill="#7A88CC" points="179.145,71.398 201.675,53.914 201.684,54.378 179.154,71.863 "/> - </g> - <g> - <path fill="#6272C3" d="M178.695,71.318l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L178.695,71.318z M179.179,117.421l-0.907-45.259 - l-44.622-7.868l0.907,45.259L179.179,117.421"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="149.42,45.327 150.327,90.585 105.697,82.716 104.79,37.458 "/> - </g> - <g> - <polygon fill="#7A88CC" points="82.294,100.966 82.285,100.5 104.814,83.016 104.824,83.482 "/> - </g> - <g> - <polygon fill="#7684CA" points="82.744,101.045 82.294,100.966 104.824,83.482 105.273,83.561 "/> - </g> - <g> - <path fill="#6272C3" d="M149.844,44.482l0.45,0.08l0.009,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.465l0.449,0.08L149.844,44.482z M150.327,90.585l-0.907-45.259l-44.63-7.869 - l0.907,45.259L150.327,90.585"/> - </g> - <g> - <polygon fill="#7A88CC" points="82.285,100.5 81.359,54.328 103.889,36.843 104.814,83.016 "/> - </g> - <g> - <polygon fill="#7A88CC" points="81.359,54.328 81.351,53.862 103.88,36.377 103.889,36.843 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="83.168,100.201 82.261,54.942 104.79,37.458 105.697,82.716 "/> - </g> - <g> - <polygon fill="#7A88CC" points="82.261,54.942 104.79,37.458 105.697,82.716 83.168,100.201 "/> - </g> - <g> - <polygon fill="#7684CA" points="81.351,53.862 103.88,36.377 104.329,36.457 81.8,53.941 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.259,109.071 82.744,101.045 105.273,83.561 150.788,91.586 "/> - </g> - <g> - <polygon fill="#7684CA" points="83.168,100.201 105.697,82.716 150.327,90.585 127.798,108.07 "/> - </g> - <g> - <polygon fill="#769AC7" points="127.798,108.07 83.168,100.201 105.697,82.716 150.327,90.585 "/> - </g> - <g> - <polygon fill="#7684CA" points="128.708,109.15 128.259,109.071 150.788,91.586 151.237,91.666 "/> - </g> - <g> - <polygon fill="#7A88CC" points="128.699,108.684 151.229,91.2 151.237,91.666 128.708,109.15 "/> - </g> - <g> - <polygon fill="#628CBE" points="126.891,62.811 127.798,108.07 83.168,100.201 82.261,54.942 "/> - </g> - <g> - <polygon fill="#769AC7" points="82.261,54.942 104.79,37.458 149.42,45.327 126.891,62.811 "/> - </g> - <g> - <polygon fill="#7684CA" points="126.891,62.811 82.261,54.942 104.79,37.458 149.42,45.327 "/> - </g> - <g> - <polygon fill="#7A88CC" points="127.798,108.07 126.891,62.811 149.42,45.327 150.327,90.585 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="126.891,62.811 149.42,45.327 150.327,90.585 127.798,108.07 "/> - </g> - <g> - <polygon fill="#7684CA" points="81.8,53.941 104.329,36.457 149.844,44.482 127.314,61.966 "/> - </g> - <g> - <polygon fill="#7A88CC" points="127.773,62.511 150.303,45.027 151.229,91.2 128.699,108.684 "/> - </g> - <g> - <polygon fill="#7684CA" points="127.314,61.966 149.844,44.482 150.294,44.562 127.764,62.046 "/> - </g> - <g> - <polygon fill="#7A88CC" points="127.764,62.046 150.294,44.562 150.303,45.027 127.773,62.511 "/> - </g> - <g> - <path fill="#6272C3" d="M127.314,61.966l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L127.314,61.966z M127.798,108.07l-0.907-45.259 - l-44.63-7.869l0.907,45.259L127.798,108.07"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="70.48,215.535 71.388,260.786 26.758,252.916 25.851,207.666 "/> - </g> - <g> - <polygon fill="#7A88CC" points="3.355,271.173 3.346,270.708 25.875,253.224 25.885,253.689 "/> - </g> - <g> - <polygon fill="#7684CA" points="3.806,271.253 3.355,271.173 25.885,253.689 26.335,253.769 "/> - </g> - <g> - <path fill="#6272C3" d="M70.904,214.69l0.45,0.08l0.01,0.457l0.926,46.181l0.009,0.466l-0.45-0.079l-45.514-8.025l-0.45-0.08 - l-0.01-0.465l-0.926-46.182l-0.009-0.457l0.45,0.079L70.904,214.69z M71.388,260.786l-0.907-45.251l-44.63-7.869l0.907,45.25 - L71.388,260.786"/> - </g> - <g> - <polygon fill="#7A88CC" points="3.346,270.708 2.42,224.527 24.949,207.042 25.875,253.224 "/> - </g> - <g> - <polygon fill="#7A88CC" points="2.42,224.527 2.411,224.07 24.94,206.585 24.949,207.042 "/> - </g> - <g> - <polygon fill="#7A88CC" points="3.321,225.15 25.851,207.666 26.758,252.916 4.229,270.4 "/> - </g> - <g> - <polygon fill="#FFDD77" points="4.229,270.4 3.321,225.15 25.851,207.666 26.758,252.916 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.411,224.07 24.94,206.585 25.391,206.665 2.861,224.149 "/> - </g> - <g> - <polygon fill="#7684CA" points="49.319,279.279 3.806,271.253 26.335,253.769 71.849,261.794 "/> - </g> - <g> - <polygon fill="#FFDC72" points="48.858,278.27 4.229,270.4 26.758,252.916 71.388,260.786 "/> - </g> - <g> - <polygon fill="#7684CA" points="4.229,270.4 26.758,252.916 71.388,260.786 48.858,278.27 "/> - </g> - <g> - <polygon fill="#7684CA" points="49.77,279.358 49.319,279.279 71.849,261.794 72.299,261.874 "/> - </g> - <g> - <polygon fill="#7A88CC" points="49.761,278.892 72.29,261.408 72.299,261.874 49.77,279.358 "/> - </g> - <g> - <polygon fill="#7684CA" points="47.951,233.019 3.321,225.15 25.851,207.666 70.48,215.535 "/> - </g> - <g> - <polygon fill="#FFDC72" points="3.321,225.15 25.851,207.666 70.48,215.535 47.951,233.019 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.858,278.27 47.951,233.019 70.48,215.535 71.388,260.786 "/> - </g> - <g> - <polygon fill="#FFDD77" points="47.951,233.019 70.48,215.535 71.388,260.786 48.858,278.27 "/> - </g> - <g> - <path fill="#6272C3" d="M48.375,232.174l0.45,0.08l0.01,0.457l0.926,46.181l0.009,0.466l-0.45-0.079l-45.514-8.025l-0.45-0.08 - l-0.01-0.465L2.42,224.527l-0.009-0.457l0.45,0.079L48.375,232.174z M48.858,278.27l-0.907-45.251l-44.63-7.869l0.907,45.25 - L48.858,278.27"/> - </g> - <g> - <polygon fill="#FFD65D" points="47.951,233.019 48.858,278.27 4.229,270.4 3.321,225.15 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.861,224.149 25.391,206.665 70.904,214.69 48.375,232.174 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.835,232.711 71.364,215.227 72.29,261.408 49.761,278.892 "/> - </g> - <g> - <polygon fill="#7684CA" points="48.375,232.174 70.904,214.69 71.354,214.77 48.825,232.254 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.825,232.254 71.354,214.77 71.364,215.227 48.835,232.711 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="223.356,243.326 224.264,288.584 179.642,280.716 178.734,235.458 "/> - </g> - <g> - <polygon fill="#7A88CC" points="156.239,298.966 156.229,298.5 178.759,281.016 178.769,281.482 "/> - </g> - <g> - <polygon fill="#7684CA" points="156.68,299.043 156.239,298.966 178.769,281.482 179.209,281.559 "/> - </g> - <g> - <path fill="#6272C3" d="M223.788,242.482l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.523-8.027 - l-0.44-0.077l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.44,0.078L223.788,242.482z M224.264,288.584l-0.907-45.259 - l-44.622-7.868l0.907,45.259L224.264,288.584"/> - </g> - <g> - <polygon fill="#7A88CC" points="156.229,298.5 155.304,252.328 177.833,234.843 178.759,281.016 "/> - </g> - <g> - <polygon fill="#7A88CC" points="155.304,252.328 155.295,251.862 177.824,234.377 177.833,234.843 "/> - </g> - <g> - <polygon fill="#7A88CC" points="156.205,252.942 178.734,235.458 179.642,280.716 157.112,298.201 "/> - </g> - <g> - <polygon fill="#FFDD77" points="157.112,298.201 156.205,252.942 178.734,235.458 179.642,280.716 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.295,251.862 177.824,234.377 178.265,234.456 155.735,251.94 "/> - </g> - <g> - <polygon fill="#7684CA" points="202.203,307.071 156.68,299.043 179.209,281.559 224.732,289.586 "/> - </g> - <g> - <polygon fill="#FFDC72" points="201.734,306.069 157.112,298.201 179.642,280.716 224.264,288.584 "/> - </g> - <g> - <polygon fill="#7684CA" points="157.112,298.201 179.642,280.716 224.264,288.584 201.734,306.069 "/> - </g> - <g> - <polygon fill="#7684CA" points="202.652,307.15 202.203,307.071 224.732,289.586 225.182,289.666 "/> - </g> - <g> - <polygon fill="#7A88CC" points="202.644,306.684 225.173,289.2 225.182,289.666 202.652,307.15 "/> - </g> - <g> - <polygon fill="#FFD65D" points="200.827,260.81 201.734,306.069 157.112,298.201 156.205,252.942 "/> - </g> - <g> - <polygon fill="#FFDC72" points="156.205,252.942 178.734,235.458 223.356,243.326 200.827,260.81 "/> - </g> - <g> - <polygon fill="#7684CA" points="200.827,260.81 156.205,252.942 178.734,235.458 223.356,243.326 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.734,306.069 200.827,260.81 223.356,243.326 224.264,288.584 "/> - </g> - <g> - <polygon fill="#FFDD77" points="200.827,260.81 223.356,243.326 224.264,288.584 201.734,306.069 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.735,251.94 178.265,234.456 223.788,242.482 201.259,259.966 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.718,260.511 224.247,243.027 225.173,289.2 202.644,306.684 "/> - </g> - <g> - <polygon fill="#7684CA" points="201.259,259.966 223.788,242.482 224.237,242.562 201.708,260.046 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.708,260.046 224.237,242.562 224.247,243.027 201.718,260.511 "/> - </g> - <g> - <path fill="#6272C3" d="M201.259,259.966l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.523-8.027 - l-0.44-0.077l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.44,0.078L201.259,259.966z M201.734,306.069l-0.907-45.259 - l-44.622-7.868l0.907,45.259L201.734,306.069"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="172.244,234.237 173.151,279.496 128.521,271.626 127.614,226.368 "/> - </g> - <g> - <polygon fill="#7A88CC" points="105.118,289.876 105.109,289.411 127.639,271.926 127.648,272.392 "/> - </g> - <g> - <polygon fill="#7684CA" points="105.568,289.956 105.118,289.876 127.648,272.392 128.098,272.471 "/> - </g> - <g> - <path fill="#6272C3" d="M172.668,233.392l0.45,0.08l0.009,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L172.668,233.392z M173.151,279.496l-0.907-45.259 - l-44.63-7.869l0.907,45.259L173.151,279.496"/> - </g> - <g> - <polygon fill="#7A88CC" points="105.109,289.411 104.184,243.238 126.713,225.753 127.639,271.926 "/> - </g> - <g> - <polygon fill="#7A88CC" points="104.184,243.238 104.175,242.772 126.704,225.288 126.713,225.753 "/> - </g> - <g> - <polygon fill="#7A88CC" points="105.085,243.852 127.614,226.368 128.521,271.626 105.992,289.111 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="105.992,289.111 105.085,243.852 127.614,226.368 128.521,271.626 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.175,242.772 126.704,225.288 127.153,225.367 104.624,242.851 "/> - </g> - <g> - <polygon fill="#7684CA" points="151.083,297.981 105.568,289.956 128.098,272.471 173.612,280.497 "/> - </g> - <g> - <polygon fill="#769AC7" points="150.622,296.98 105.992,289.111 128.521,271.626 173.151,279.496 "/> - </g> - <g> - <polygon fill="#7684CA" points="105.992,289.111 128.521,271.626 173.151,279.496 150.622,296.98 "/> - </g> - <g> - <polygon fill="#7684CA" points="151.532,298.06 151.083,297.981 173.612,280.497 174.062,280.576 "/> - </g> - <g> - <polygon fill="#7A88CC" points="151.523,297.594 174.053,280.11 174.062,280.576 151.532,298.06 "/> - </g> - <g> - <polygon fill="#628CBE" points="149.715,251.721 150.622,296.98 105.992,289.111 105.085,243.852 "/> - </g> - <g> - <polygon fill="#769AC7" points="105.085,243.852 127.614,226.368 172.244,234.237 149.715,251.721 "/> - </g> - <g> - <polygon fill="#7684CA" points="149.715,251.721 105.085,243.852 127.614,226.368 172.244,234.237 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.622,296.98 149.715,251.721 172.244,234.237 173.151,279.496 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="149.715,251.721 172.244,234.237 173.151,279.496 150.622,296.98 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.624,242.851 127.153,225.367 172.668,233.392 150.139,250.876 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.598,251.421 173.127,233.937 174.053,280.11 151.523,297.594 "/> - </g> - <g> - <polygon fill="#7684CA" points="150.139,250.876 172.668,233.392 173.118,233.472 150.588,250.957 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.588,250.957 173.118,233.472 173.127,233.937 150.598,251.421 "/> - </g> - <g> - <path fill="#6272C3" d="M150.139,250.876l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L150.139,250.876z M150.622,296.98l-0.907-45.259 - l-44.63-7.869l0.907,45.259L150.622,296.98"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M121.287,224.042l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.466l-0.449-0.079l-45.522-8.027 - l-0.441-0.077l-0.01-0.466l-0.926-46.183l-0.009-0.456l0.441,0.078L121.287,224.042z M121.771,270.136l-0.907-45.25 - l-44.63-7.869l0.907,45.25L121.771,270.136"/> - </g> - <g> - <polygon fill="#628CBE" points="120.863,224.886 121.771,270.136 77.141,262.267 76.233,217.017 "/> - </g> - <g> - <polygon fill="#7A88CC" points="53.737,280.525 53.729,280.06 76.258,262.576 76.268,263.042 "/> - </g> - <g> - <polygon fill="#7684CA" points="54.18,280.603 53.737,280.525 76.268,263.042 76.709,263.119 "/> - </g> - <g> - <polygon fill="#7A88CC" points="53.729,280.06 52.803,233.877 75.332,216.393 76.258,262.576 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.803,233.877 52.794,233.421 75.323,215.937 75.332,216.393 "/> - </g> - <g> - <polygon fill="#7A88CC" points="53.704,234.501 76.233,217.017 77.141,262.267 54.61,279.75 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="54.61,279.75 53.704,234.501 76.233,217.017 77.141,262.267 "/> - </g> - <g> - <polygon fill="#7684CA" points="52.794,233.421 75.323,215.937 75.765,216.015 53.235,233.5 "/> - </g> - <g> - <polygon fill="#7684CA" points="99.702,288.63 54.18,280.603 76.709,263.119 122.231,271.146 "/> - </g> - <g> - <polygon fill="#769AC7" points="99.241,287.621 54.61,279.75 77.141,262.267 121.771,270.136 "/> - </g> - <g> - <polygon fill="#7684CA" points="54.61,279.75 77.141,262.267 121.771,270.136 99.241,287.621 "/> - </g> - <g> - <polygon fill="#7684CA" points="100.151,288.709 99.702,288.63 122.231,271.146 122.681,271.225 "/> - </g> - <g> - <polygon fill="#7A88CC" points="100.143,288.244 122.672,270.759 122.681,271.225 100.151,288.709 "/> - </g> - <g> - <polygon fill="#628CBE" points="98.334,242.371 99.241,287.621 54.61,279.75 53.704,234.501 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.334,242.371 53.704,234.501 76.233,217.017 120.863,224.886 "/> - </g> - <g> - <polygon fill="#769AC7" points="53.704,234.501 76.233,217.017 120.863,224.886 98.334,242.371 "/> - </g> - <g> - <polygon fill="#7A88CC" points="99.241,287.621 98.334,242.371 120.863,224.886 121.771,270.136 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="98.334,242.371 120.863,224.886 121.771,270.136 99.241,287.621 "/> - </g> - <g> - <polygon fill="#7684CA" points="53.235,233.5 75.765,216.015 121.287,224.042 98.758,241.526 "/> - </g> - <g> - <polygon fill="#7A88CC" points="99.217,242.062 121.746,224.578 122.672,270.759 100.143,288.244 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.758,241.526 121.287,224.042 121.737,224.121 99.207,241.605 "/> - </g> - <g> - <polygon fill="#7A88CC" points="99.207,241.605 121.737,224.121 121.746,224.578 99.217,242.062 "/> - </g> - <g> - <path fill="#6272C3" d="M98.758,241.526l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.466l-0.449-0.079l-45.522-8.027 - l-0.442-0.078l-0.009-0.465l-0.926-46.183l-0.009-0.456l0.441,0.078L98.758,241.526z M99.241,287.621l-0.907-45.25 - l-44.63-7.869l0.906,45.249L99.241,287.621"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M69.905,163.687l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.522-8.027 - l-0.441-0.077l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.441,0.078L69.905,163.687z M70.381,209.79l-0.907-45.259 - l-44.622-7.868l0.907,45.259L70.381,209.79"/> - </g> - <g> - <polygon fill="#FFD65D" points="69.474,164.531 70.381,209.79 25.759,201.921 24.852,156.663 "/> - </g> - <g> - <polygon fill="#7A88CC" points="2.356,220.171 2.347,219.706 24.876,202.221 24.886,202.687 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.798,220.249 2.356,220.171 24.886,202.687 25.327,202.764 "/> - </g> - <g> - <polygon fill="#7A88CC" points="2.347,219.706 1.421,173.533 23.95,156.048 24.876,202.221 "/> - </g> - <g> - <polygon fill="#7A88CC" points="1.421,173.533 1.412,173.067 23.941,155.583 23.95,156.048 "/> - </g> - <g> - <polygon fill="#FFDD77" points="3.229,219.406 2.322,174.147 24.852,156.663 25.759,201.921 "/> - </g> - <g> - <polygon fill="#7A88CC" points="2.322,174.147 24.852,156.663 25.759,201.921 3.229,219.406 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.412,173.067 23.941,155.583 24.383,155.661 1.854,173.145 "/> - </g> - <g> - <polygon fill="#7684CA" points="48.32,228.276 2.798,220.249 25.327,202.764 70.85,210.792 "/> - </g> - <g> - <polygon fill="#FFDC72" points="47.852,227.274 3.229,219.406 25.759,201.921 70.381,209.79 "/> - </g> - <g> - <polygon fill="#7684CA" points="3.229,219.406 25.759,201.921 70.381,209.79 47.852,227.274 "/> - </g> - <g> - <polygon fill="#7684CA" points="48.77,228.355 48.32,228.276 70.85,210.792 71.299,210.871 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.761,227.889 71.29,210.405 71.299,210.871 48.77,228.355 "/> - </g> - <g> - <polygon fill="#FFD65D" points="46.944,182.015 47.852,227.274 3.229,219.406 2.322,174.147 "/> - </g> - <g> - <polygon fill="#7684CA" points="46.944,182.015 2.322,174.147 24.852,156.663 69.474,164.531 "/> - </g> - <g> - <polygon fill="#FFDC72" points="2.322,174.147 24.852,156.663 69.474,164.531 46.944,182.015 "/> - </g> - <g> - <polygon fill="#FFDD77" points="46.944,182.015 69.474,164.531 70.381,209.79 47.852,227.274 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.852,227.274 46.944,182.015 69.474,164.531 70.381,209.79 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.854,173.145 24.383,155.661 69.905,163.687 47.376,181.171 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.835,181.716 70.364,164.232 71.29,210.405 48.761,227.889 "/> - </g> - <g> - <polygon fill="#7684CA" points="47.376,181.171 69.905,163.687 70.354,163.767 47.825,181.251 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.825,181.251 70.354,163.767 70.364,164.232 47.835,181.716 "/> - </g> - <g> - <path fill="#6272C3" d="M47.376,181.171l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.522-8.027 - l-0.441-0.077l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.441,0.078L47.376,181.171z M47.852,227.274l-0.907-45.259 - l-44.622-7.868l0.907,45.259L47.852,227.274"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="222.356,192.323 223.264,237.582 178.642,229.713 177.734,184.456 "/> - </g> - <g> - <polygon fill="#7A88CC" points="155.239,247.963 155.229,247.499 177.76,230.014 177.769,230.479 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.681,248.042 155.239,247.963 177.769,230.479 178.211,230.557 "/> - </g> - <g> - <path fill="#6272C3" d="M222.788,191.48l0.442,0.078l0.009,0.465l0.926,46.174l0.01,0.465l-0.442-0.078l-45.521-8.026 - l-0.442-0.078l-0.009-0.465l-0.926-46.174l-0.01-0.465l0.442,0.078L222.788,191.48z M223.264,237.582l-0.907-45.259 - l-44.622-7.867l0.907,45.258L223.264,237.582"/> - </g> - <g> - <polygon fill="#7A88CC" points="155.229,247.499 154.305,201.325 176.834,183.84 177.76,230.014 "/> - </g> - <g> - <polygon fill="#7A88CC" points="154.305,201.325 154.295,200.86 176.824,183.375 176.834,183.84 "/> - </g> - <g> - <polygon fill="#7A88CC" points="155.205,201.94 177.734,184.456 178.642,229.713 156.112,247.198 "/> - </g> - <g> - <polygon fill="#FFDD77" points="156.112,247.198 155.205,201.94 177.734,184.456 178.642,229.713 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.295,200.86 176.824,183.375 177.267,183.454 154.737,200.938 "/> - </g> - <g> - <polygon fill="#7684CA" points="201.203,256.068 155.681,248.042 178.211,230.557 223.732,238.583 "/> - </g> - <g> - <polygon fill="#FFDC72" points="200.734,255.066 156.112,247.198 178.642,229.713 223.264,237.582 "/> - </g> - <g> - <polygon fill="#7684CA" points="156.112,247.198 178.642,229.713 223.264,237.582 200.734,255.066 "/> - </g> - <g> - <polygon fill="#7684CA" points="201.646,256.146 201.203,256.068 223.732,238.583 224.175,238.662 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.636,255.681 224.165,238.197 224.175,238.662 201.646,256.146 "/> - </g> - <g> - <polygon fill="#FFD65D" points="199.827,209.807 200.734,255.066 156.112,247.198 155.205,201.94 "/> - </g> - <g> - <polygon fill="#7684CA" points="199.827,209.807 155.205,201.94 177.734,184.456 222.356,192.323 "/> - </g> - <g> - <polygon fill="#FFDC72" points="155.205,201.94 177.734,184.456 222.356,192.323 199.827,209.807 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.734,255.066 199.827,209.807 222.356,192.323 223.264,237.582 "/> - </g> - <g> - <polygon fill="#FFDD77" points="199.827,209.807 222.356,192.323 223.264,237.582 200.734,255.066 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.737,200.938 177.267,183.454 222.788,191.48 200.259,208.964 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.71,209.507 223.239,192.023 224.165,238.197 201.636,255.681 "/> - </g> - <g> - <polygon fill="#7684CA" points="200.259,208.964 222.788,191.48 223.23,191.558 200.701,209.042 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.701,209.042 223.23,191.558 223.239,192.023 200.71,209.507 "/> - </g> - <g> - <path fill="#6272C3" d="M200.259,208.964l0.442,0.078l0.009,0.465l0.926,46.174l0.01,0.465l-0.442-0.078l-45.522-8.026 - l-0.441-0.078l-0.01-0.465l-0.925-46.174l-0.01-0.465l0.442,0.078L200.259,208.964z M200.734,255.066l-0.907-45.259 - l-44.622-7.867l0.907,45.258L200.734,255.066"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="171.245,183.243 172.152,228.494 127.522,220.624 126.615,175.374 "/> - </g> - <g> - <polygon fill="#7A88CC" points="104.119,238.874 104.11,238.417 126.64,220.932 126.649,221.389 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.569,238.953 104.119,238.874 126.649,221.389 127.099,221.468 "/> - </g> - <g> - <path fill="#6272C3" d="M171.669,182.398l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L171.669,182.398z M172.152,228.494l-0.907-45.251 - l-44.63-7.869l0.907,45.25L172.152,228.494"/> - </g> - <g> - <polygon fill="#7A88CC" points="104.11,238.417 103.185,192.235 125.714,174.75 126.64,220.932 "/> - </g> - <g> - <polygon fill="#7A88CC" points="103.185,192.235 103.176,191.778 125.705,174.293 125.714,174.75 "/> - </g> - <g> - <polygon fill="#7A88CC" points="104.086,192.857 126.615,175.374 127.522,220.624 104.993,238.108 "/> - </g> - <g> - <polygon fill="#FFDD77" points="104.993,238.108 104.086,192.857 126.615,175.374 127.522,220.624 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.176,191.778 125.705,174.293 126.154,174.373 103.625,191.857 "/> - </g> - <g> - <polygon fill="#7684CA" points="150.084,246.978 104.569,238.953 127.099,221.468 172.613,229.494 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.993,238.108 127.522,220.624 172.152,228.494 149.623,245.978 "/> - </g> - <g> - <polygon fill="#FFDC72" points="149.623,245.977 104.993,238.108 127.522,220.624 172.152,228.494 "/> - </g> - <g> - <polygon fill="#7684CA" points="150.533,247.057 150.084,246.978 172.613,229.494 173.063,229.573 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.524,246.6 173.054,229.116 173.063,229.573 150.533,247.057 "/> - </g> - <g> - <polygon fill="#7684CA" points="148.716,200.727 104.086,192.857 126.615,175.374 171.245,183.243 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.623,245.978 148.716,200.727 171.245,183.243 172.152,228.494 "/> - </g> - <g> - <polygon fill="#FFDC72" points="104.086,192.857 126.615,175.374 171.245,183.243 148.716,200.727 "/> - </g> - <g> - <polygon fill="#FFD65D" points="148.716,200.727 149.623,245.977 104.993,238.108 104.086,192.857 "/> - </g> - <g> - <polygon fill="#FFDD77" points="148.716,200.727 171.245,183.243 172.152,228.494 149.623,245.977 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.625,191.857 126.154,174.373 171.669,182.398 149.14,199.882 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.599,200.418 172.128,182.934 173.054,229.116 150.524,246.6 "/> - </g> - <g> - <polygon fill="#7684CA" points="149.14,199.882 171.669,182.398 172.119,182.477 149.589,199.961 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.589,199.961 172.119,182.477 172.128,182.934 149.599,200.418 "/> - </g> - <g> - <path fill="#6272C3" d="M149.14,199.882l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L149.14,199.882z M149.623,245.978l-0.907-45.251 - l-44.63-7.87l0.907,45.251L149.623,245.978"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="119.856,173.881 120.764,219.141 76.142,211.273 75.234,166.013 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.738,229.522 52.729,229.056 75.259,211.572 75.269,212.039 "/> - </g> - <g> - <polygon fill="#7684CA" points="53.18,229.6 52.738,229.522 75.269,212.039 75.709,212.116 "/> - </g> - <g> - <path fill="#6272C3" d="M120.288,173.038l0.449,0.08l0.01,0.466l0.926,46.172l0.009,0.467l-0.449-0.079l-45.523-8.027 - l-0.44-0.077l-0.01-0.467L74.333,165.4l-0.009-0.467l0.44,0.078L120.288,173.038z M120.764,219.141l-0.907-45.26l-44.622-7.868 - l0.907,45.26L120.764,219.141"/> - </g> - <g> - <polygon fill="#7A88CC" points="52.729,229.056 51.804,182.884 74.333,165.4 75.259,211.572 "/> - </g> - <g> - <polygon fill="#7A88CC" points="51.804,182.884 51.794,182.417 74.324,164.933 74.333,165.4 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.705,183.498 75.234,166.013 76.142,211.273 53.612,228.757 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="53.612,228.757 52.705,183.498 75.234,166.013 76.142,211.273 "/> - </g> - <g> - <polygon fill="#7684CA" points="51.794,182.417 74.324,164.933 74.765,165.011 52.235,182.496 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.703,237.627 53.18,229.6 75.709,212.116 121.232,220.143 "/> - </g> - <g> - <polygon fill="#7684CA" points="53.612,228.757 76.142,211.273 120.764,219.141 98.234,236.625 "/> - </g> - <g> - <polygon fill="#769AC7" points="98.234,236.625 53.612,228.757 76.142,211.273 120.764,219.141 "/> - </g> - <g> - <polygon fill="#7684CA" points="99.152,237.707 98.703,237.627 121.232,220.143 121.682,220.222 "/> - </g> - <g> - <polygon fill="#7A88CC" points="99.144,237.24 121.673,219.755 121.682,220.222 99.152,237.707 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.327,191.366 52.705,183.498 75.234,166.013 119.856,173.881 "/> - </g> - <g> - <polygon fill="#7A88CC" points="98.234,236.625 97.327,191.366 119.856,173.881 120.764,219.141 "/> - </g> - <g> - <polygon fill="#769AC7" points="52.705,183.498 75.234,166.013 119.856,173.881 97.327,191.366 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="97.327,191.366 119.856,173.881 120.764,219.141 98.234,236.625 "/> - </g> - <g> - <path fill="#6272C3" d="M97.759,190.522l0.449,0.08l0.01,0.466l0.926,46.172l0.009,0.467l-0.449-0.079L53.18,229.6 - l-0.441-0.078l-0.009-0.466l-0.926-46.172l-0.01-0.467l0.441,0.078L97.759,190.522z M98.234,236.625l-0.907-45.26 - l-44.622-7.868l0.907,45.26L98.234,236.625"/> - </g> - <g> - <polygon fill="#628CBE" points="97.327,191.366 98.234,236.625 53.612,228.757 52.705,183.498 "/> - </g> - <g> - <polygon fill="#7684CA" points="52.235,182.496 74.765,165.011 120.288,173.038 97.759,190.522 "/> - </g> - <g> - <polygon fill="#7A88CC" points="98.218,191.068 120.747,173.583 121.673,219.755 99.144,237.24 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.759,190.522 120.288,173.038 120.737,173.118 98.208,190.602 "/> - </g> - <g> - <polygon fill="#7A88CC" points="98.208,190.602 120.737,173.118 120.747,173.583 98.218,191.068 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M69.491,112.513l0.451,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L69.491,112.513z M69.976,158.609l-0.907-45.251 - l-44.63-7.869l0.907,45.25L69.976,158.609"/> - </g> - <g> - <polygon fill="#FFD65D" points="69.068,113.358 69.976,158.609 25.346,150.739 24.438,105.489 "/> - </g> - <g> - <polygon fill="#7A88CC" points="1.942,168.989 1.934,168.532 24.463,151.047 24.473,151.504 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.393,169.068 1.942,168.989 24.473,151.504 24.922,151.583 "/> - </g> - <g> - <polygon fill="#7A88CC" points="1.934,168.532 1.008,122.35 23.537,104.866 24.463,151.047 "/> - </g> - <g> - <polygon fill="#7A88CC" points="1.008,122.35 0.999,121.893 23.528,104.409 23.537,104.866 "/> - </g> - <g> - <polygon fill="#FFDD77" points="2.815,168.223 1.909,122.972 24.438,105.489 25.346,150.739 "/> - </g> - <g> - <polygon fill="#7A88CC" points="1.909,122.972 24.438,105.489 25.346,150.739 2.815,168.223 "/> - </g> - <g> - <polygon fill="#7684CA" points="0.999,121.893 23.528,104.409 23.978,104.488 1.448,121.972 "/> - </g> - <g> - <polygon fill="#7684CA" points="47.906,177.093 2.393,169.068 24.922,151.583 70.436,159.609 "/> - </g> - <g> - <polygon fill="#7684CA" points="2.815,168.223 25.346,150.739 69.976,158.609 47.446,176.092 "/> - </g> - <g> - <polygon fill="#FFDC72" points="47.446,176.092 2.815,168.223 25.346,150.739 69.976,158.609 "/> - </g> - <g> - <polygon fill="#7684CA" points="48.356,177.172 47.906,177.093 70.436,159.609 70.886,159.688 "/> - </g> - <g> - <polygon fill="#7A88CC" points="48.348,176.715 70.877,159.231 70.886,159.688 48.356,177.172 "/> - </g> - <g> - <polygon fill="#FFD65D" points="46.539,130.842 47.446,176.092 2.815,168.223 1.909,122.972 "/> - </g> - <g> - <polygon fill="#7684CA" points="46.539,130.842 1.909,122.972 24.438,105.489 69.068,113.358 "/> - </g> - <g> - <polygon fill="#FFDC72" points="1.909,122.972 24.438,105.489 69.068,113.358 46.539,130.842 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.446,176.092 46.539,130.842 69.068,113.358 69.976,158.609 "/> - </g> - <g> - <polygon fill="#FFDD77" points="46.539,130.842 69.068,113.358 69.976,158.609 47.446,176.092 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.448,121.972 23.978,104.488 69.491,112.513 46.962,129.998 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.422,130.534 69.951,113.049 70.877,159.231 48.348,176.715 "/> - </g> - <g> - <polygon fill="#7684CA" points="46.962,129.998 69.491,112.513 69.942,112.592 47.412,130.077 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.412,130.077 69.942,112.592 69.951,113.049 47.422,130.534 "/> - </g> - <g> - <path fill="#6272C3" d="M46.962,129.998l0.45,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.45-0.079l-45.514-8.025 - l-0.45-0.079l-0.009-0.457L1.008,122.35l-0.009-0.457l0.449,0.079L46.962,129.998z M47.446,176.092l-0.907-45.25l-44.63-7.87 - l0.906,45.251L47.446,176.092"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="221.951,141.15 222.858,186.409 178.229,178.54 177.321,133.281 "/> - </g> - <g> - <polygon fill="#7A88CC" points="154.825,196.789 154.816,196.324 177.346,178.839 177.355,179.305 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.268,196.867 154.825,196.789 177.355,179.305 177.797,179.382 "/> - </g> - <g> - <path fill="#6272C3" d="M222.375,140.305l0.449,0.08l0.01,0.465l0.926,46.173l0.009,0.466l-0.449-0.079l-45.522-8.027 - l-0.441-0.077l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.441,0.078L222.375,140.305z M222.858,186.409l-0.907-45.259 - l-44.63-7.869l0.907,45.259L222.858,186.409"/> - </g> - <g> - <polygon fill="#7A88CC" points="154.816,196.324 153.891,150.151 176.42,132.667 177.346,178.839 "/> - </g> - <g> - <polygon fill="#7A88CC" points="153.891,150.151 153.882,149.685 176.411,132.201 176.42,132.667 "/> - </g> - <g> - <polygon fill="#7A88CC" points="154.792,150.765 177.321,133.281 178.229,178.54 155.699,196.024 "/> - </g> - <g> - <polygon fill="#FFDD77" points="155.699,196.024 154.792,150.765 177.321,133.281 178.229,178.54 "/> - </g> - <g> - <polygon fill="#7684CA" points="153.882,149.685 176.411,132.201 176.853,132.279 154.323,149.763 "/> - </g> - <g> - <polygon fill="#7684CA" points="200.79,204.894 155.268,196.867 177.797,179.382 223.319,187.41 "/> - </g> - <g> - <polygon fill="#FFDC72" points="200.329,203.893 155.699,196.024 178.229,178.54 222.858,186.409 "/> - </g> - <g> - <polygon fill="#7684CA" points="155.699,196.024 178.229,178.54 222.858,186.409 200.329,203.893 "/> - </g> - <g> - <polygon fill="#7684CA" points="201.239,204.973 200.79,204.894 223.319,187.41 223.769,187.489 "/> - </g> - <g> - <polygon fill="#7A88CC" points="201.23,204.507 223.76,187.023 223.769,187.489 201.239,204.973 "/> - </g> - <g> - <polygon fill="#FFD65D" points="199.422,158.634 200.329,203.893 155.699,196.024 154.792,150.765 "/> - </g> - <g> - <polygon fill="#7684CA" points="199.422,158.634 154.792,150.765 177.321,133.281 221.951,141.15 "/> - </g> - <g> - <polygon fill="#FFDC72" points="154.792,150.765 177.321,133.281 221.951,141.15 199.422,158.634 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.329,203.893 199.422,158.634 221.951,141.15 222.858,186.409 "/> - </g> - <g> - <polygon fill="#FFDD77" points="199.422,158.634 221.951,141.15 222.858,186.409 200.329,203.893 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.323,149.763 176.853,132.279 222.375,140.305 199.846,157.79 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.305,158.334 222.834,140.85 223.76,187.023 201.23,204.507 "/> - </g> - <g> - <polygon fill="#7684CA" points="199.846,157.79 222.375,140.305 222.824,140.385 200.295,157.869 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.295,157.869 222.824,140.385 222.834,140.85 200.305,158.334 "/> - </g> - <g> - <path fill="#6272C3" d="M199.846,157.79l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.522-8.027 - l-0.442-0.078l-0.009-0.465l-0.926-46.173l-0.009-0.466l0.441,0.078L199.846,157.79z M200.329,203.893l-0.907-45.259 - l-44.63-7.869l0.907,45.259L200.329,203.893"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M171.255,131.215l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.449,0.079L171.255,131.215z M171.738,177.319l-0.907-45.259 - l-44.63-7.869l0.907,45.259L171.738,177.319"/> - </g> - <g> - <polygon fill="#628CBE" points="170.831,132.06 171.738,177.319 127.108,169.45 126.201,124.191 "/> - </g> - <g> - <polygon fill="#7A88CC" points="103.706,187.699 103.696,187.234 126.226,169.75 126.235,170.215 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.155,187.779 103.706,187.699 126.235,170.215 126.685,170.294 "/> - </g> - <g> - <polygon fill="#7A88CC" points="103.696,187.234 102.771,141.061 125.3,123.577 126.226,169.75 "/> - </g> - <g> - <polygon fill="#7A88CC" points="102.771,141.061 102.762,140.595 125.291,123.111 125.3,123.577 "/> - </g> - <g> - <polygon fill="#7A88CC" points="103.671,141.675 126.201,124.191 127.108,169.45 104.579,186.934 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="104.579,186.934 103.672,141.675 126.201,124.191 127.108,169.45 "/> - </g> - <g> - <polygon fill="#7684CA" points="102.762,140.595 125.291,123.111 125.74,123.19 103.211,140.674 "/> - </g> - <g> - <polygon fill="#7684CA" points="149.67,195.804 104.155,187.779 126.685,170.294 172.199,178.32 "/> - </g> - <g> - <polygon fill="#769AC7" points="149.209,194.803 104.579,186.934 127.108,169.45 171.738,177.319 "/> - </g> - <g> - <polygon fill="#7684CA" points="104.579,186.934 127.108,169.45 171.738,177.319 149.209,194.803 "/> - </g> - <g> - <polygon fill="#7684CA" points="150.119,195.883 149.67,195.804 172.199,178.32 172.648,178.399 "/> - </g> - <g> - <polygon fill="#7A88CC" points="150.11,195.417 172.64,177.933 172.648,178.399 150.119,195.883 "/> - </g> - <g> - <polygon fill="#7684CA" points="148.302,149.544 103.671,141.675 126.201,124.191 170.831,132.06 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.209,194.803 148.302,149.544 170.831,132.06 171.738,177.319 "/> - </g> - <g> - <polygon fill="#769AC7" points="103.672,141.675 126.201,124.191 170.831,132.06 148.302,149.544 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="148.302,149.544 170.831,132.06 171.738,177.319 149.209,194.803 "/> - </g> - <g> - <path fill="#6272C3" d="M148.726,148.7l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.08l-0.01-0.465l-0.926-46.173l-0.009-0.466l0.449,0.079L148.726,148.7z M149.209,194.803l-0.907-45.259 - l-44.631-7.869l0.908,45.259L149.209,194.803"/> - </g> - <g> - <polygon fill="#628CBE" points="148.302,149.544 149.209,194.803 104.579,186.934 103.672,141.675 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.211,140.674 125.74,123.19 171.255,131.215 148.726,148.7 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.185,149.245 171.714,131.76 172.64,177.933 150.11,195.417 "/> - </g> - <g> - <polygon fill="#7684CA" points="148.726,148.7 171.255,131.215 171.704,131.294 149.175,148.779 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.175,148.779 171.704,131.294 171.714,131.76 149.185,149.245 "/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#FFD65D" points="119.45,122.708 120.357,167.967 75.728,160.098 74.82,114.839 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.325,178.347 52.315,177.882 74.845,160.398 74.854,160.863 "/> - </g> - <g> - <polygon fill="#7684CA" points="52.774,178.427 52.325,178.347 74.854,160.863 75.304,160.943 "/> - </g> - <g> - <path fill="#6272C3" d="M119.874,121.864l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.08l-0.01-0.465l-0.926-46.173l-0.009-0.466l0.449,0.079L119.874,121.864z M120.357,167.967l-0.907-45.259 - l-44.63-7.869l0.907,45.259L120.357,167.967"/> - </g> - <g> - <polygon fill="#7A88CC" points="52.315,177.882 51.39,131.709 73.919,114.225 74.845,160.398 "/> - </g> - <g> - <polygon fill="#7A88CC" points="51.39,131.709 51.381,131.244 73.91,113.759 73.919,114.225 "/> - </g> - <g> - <polygon fill="#7A88CC" points="52.291,132.324 74.82,114.839 75.728,160.098 53.198,177.583 "/> - </g> - <g> - <polygon fill="#FFDD77" points="53.198,177.583 52.291,132.324 74.82,114.839 75.728,160.098 "/> - </g> - <g> - <polygon fill="#7684CA" points="51.381,131.244 73.91,113.759 74.359,113.838 51.83,131.323 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.289,186.453 52.774,178.427 75.304,160.943 120.818,168.968 "/> - </g> - <g> - <polygon fill="#FFDC72" points="97.828,185.452 53.198,177.583 75.728,160.098 120.357,167.967 "/> - </g> - <g> - <polygon fill="#7684CA" points="53.198,177.583 75.728,160.098 120.357,167.967 97.828,185.452 "/> - </g> - <g> - <polygon fill="#7684CA" points="98.738,186.532 98.289,186.453 120.818,168.968 121.268,169.047 "/> - </g> - <g> - <polygon fill="#7A88CC" points="98.729,186.066 121.259,168.582 121.268,169.047 98.738,186.532 "/> - </g> - <g> - <polygon fill="#FFD65D" points="96.921,140.193 97.828,185.452 53.198,177.583 52.291,132.324 "/> - </g> - <g> - <polygon fill="#7684CA" points="96.921,140.193 52.291,132.324 74.82,114.839 119.45,122.708 "/> - </g> - <g> - <polygon fill="#FFDC72" points="52.291,132.324 74.82,114.839 119.45,122.708 96.921,140.193 "/> - </g> - <g> - <polygon fill="#FFDD77" points="96.921,140.193 119.45,122.708 120.357,167.967 97.828,185.452 "/> - </g> - <g> - <polygon fill="#7A88CC" points="97.828,185.452 96.921,140.193 119.45,122.708 120.357,167.967 "/> - </g> - <g> - <polygon fill="#7684CA" points="51.83,131.323 74.359,113.838 119.874,121.864 97.345,139.348 "/> - </g> - <g> - <polygon fill="#7A88CC" points="97.804,139.893 120.333,122.409 121.259,168.582 98.729,186.066 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.345,139.348 119.874,121.864 120.323,121.943 97.794,139.427 "/> - </g> - <g> - <polygon fill="#7A88CC" points="97.794,139.427 120.323,121.943 120.333,122.409 97.804,139.893 "/> - </g> - <g> - <path fill="#6272C3" d="M97.345,139.348l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.515-8.025 - l-0.449-0.08l-0.01-0.465l-0.926-46.173l-0.009-0.466l0.449,0.079L97.345,139.348z M97.828,185.452l-0.907-45.259l-44.63-7.869 - l0.907,45.259L97.828,185.452"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M68.493,61.51l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.523-8.027 - l-0.44-0.077l-0.01-0.466l-0.926-46.173l-0.009-0.466l0.44,0.078L68.493,61.51z M68.968,107.613l-0.907-45.259l-44.621-7.868 - l0.907,45.259L68.968,107.613"/> - </g> - <g> - <polygon fill="#FFD65D" points="68.061,62.354 68.968,107.613 24.347,99.745 23.439,54.486 "/> - </g> - <g> - <polygon fill="#7A88CC" points="0.944,117.994 0.935,117.529 23.464,100.044 23.474,100.51 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.385,118.072 0.944,117.994 23.474,100.51 23.914,100.587 "/> - </g> - <g> - <polygon fill="#7A88CC" points="0.935,117.529 0.009,71.356 22.538,53.872 23.464,100.044 "/> - </g> - <g> - <polygon fill="#7A88CC" points="0.009,71.356 0,70.89 22.529,53.406 22.538,53.872 "/> - </g> - <g> - <polygon fill="#7A88CC" points="0.91,71.97 23.439,54.486 24.347,99.745 1.817,117.229 "/> - </g> - <g> - <polygon fill="#FFDD77" points="1.817,117.229 0.91,71.97 23.439,54.486 24.347,99.745 "/> - </g> - <g> - <polygon fill="#7684CA" points="0,70.89 22.529,53.406 22.97,53.484 0.44,70.968 "/> - </g> - <g> - <polygon fill="#7684CA" points="46.908,126.099 1.385,118.072 23.914,100.587 69.438,108.615 "/> - </g> - <g> - <polygon fill="#FFDC72" points="46.438,125.097 1.817,117.229 24.347,99.745 68.968,107.613 "/> - </g> - <g> - <polygon fill="#7684CA" points="1.817,117.229 24.347,99.745 68.968,107.613 46.438,125.097 "/> - </g> - <g> - <polygon fill="#7684CA" points="47.357,126.178 46.908,126.099 69.438,108.615 69.887,108.694 "/> - </g> - <g> - <polygon fill="#7A88CC" points="47.349,125.712 69.878,108.228 69.887,108.694 47.357,126.178 "/> - </g> - <g> - <polygon fill="#FFD65D" points="45.531,79.838 46.438,125.097 1.817,117.229 0.91,71.97 "/> - </g> - <g> - <polygon fill="#7684CA" points="45.531,79.838 0.91,71.97 23.439,54.486 68.061,62.354 "/> - </g> - <g> - <polygon fill="#FFDC72" points="0.91,71.97 23.439,54.486 68.061,62.354 45.531,79.838 "/> - </g> - <g> - <polygon fill="#7A88CC" points="46.438,125.097 45.531,79.838 68.061,62.354 68.968,107.613 "/> - </g> - <g> - <polygon fill="#FFDD77" points="45.531,79.838 68.061,62.354 68.968,107.613 46.438,125.097 "/> - </g> - <g> - <polygon fill="#7684CA" points="0.44,70.968 22.97,53.484 68.493,61.51 45.964,78.995 "/> - </g> - <g> - <polygon fill="#7A88CC" points="46.423,79.54 68.952,62.055 69.878,108.228 47.349,125.712 "/> - </g> - <g> - <polygon fill="#7684CA" points="45.964,78.995 68.493,61.51 68.942,61.589 46.413,79.074 "/> - </g> - <g> - <polygon fill="#7A88CC" points="46.413,79.074 68.942,61.589 68.952,62.055 46.423,79.54 "/> - </g> - <g> - <path fill="#6272C3" d="M45.964,78.995l0.449,0.079l0.01,0.466l0.926,46.173l0.009,0.466l-0.449-0.079l-45.523-8.027 - l-0.44-0.078l-0.01-0.465L0.009,71.356L0,70.89l0.44,0.078L45.964,78.995z M46.438,125.097l-0.907-45.259L0.91,71.97 - l0.907,45.259L46.438,125.097"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M221.376,89.302l0.449,0.08l0.01,0.465l0.926,46.182l0.009,0.457l-0.449-0.079l-45.522-8.027 - l-0.441-0.077l-0.01-0.458l-0.926-46.181l-0.009-0.466l0.441,0.078L221.376,89.302z M221.852,135.405l-0.907-45.259 - l-44.622-7.868l0.907,45.259L221.852,135.405"/> - </g> - <g> - <polygon fill="#FFD65D" points="220.944,90.146 221.852,135.405 177.229,127.537 176.322,82.278 "/> - </g> - <g> - <polygon fill="#7A88CC" points="153.826,145.787 153.817,145.329 176.347,127.844 176.356,128.302 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.269,145.864 153.826,145.787 176.356,128.302 176.798,128.379 "/> - </g> - <g> - <polygon fill="#7A88CC" points="153.817,145.329 152.892,99.148 175.421,81.664 176.347,127.844 "/> - </g> - <g> - <polygon fill="#7A88CC" points="152.892,99.148 152.882,98.682 175.412,81.198 175.421,81.664 "/> - </g> - <g> - <polygon fill="#7A88CC" points="153.793,99.762 176.322,82.278 177.229,127.537 154.7,145.021 "/> - </g> - <g> - <polygon fill="#FFDD77" points="154.7,145.021 153.793,99.762 176.322,82.278 177.229,127.537 "/> - </g> - <g> - <polygon fill="#7684CA" points="152.882,98.682 175.412,81.198 175.854,81.276 153.324,98.76 "/> - </g> - <g> - <polygon fill="#7684CA" points="199.791,153.891 154.269,145.864 176.798,128.379 222.32,136.407 "/> - </g> - <g> - <polygon fill="#FFDC72" points="199.322,152.889 154.7,145.021 177.229,127.537 221.852,135.405 "/> - </g> - <g> - <polygon fill="#7684CA" points="154.7,145.021 177.229,127.537 221.852,135.405 199.322,152.889 "/> - </g> - <g> - <polygon fill="#7684CA" points="200.24,153.97 199.791,153.891 222.32,136.407 222.77,136.486 "/> - </g> - <g> - <polygon fill="#7A88CC" points="200.231,153.513 222.761,136.029 222.77,136.486 200.24,153.97 "/> - </g> - <g> - <polygon fill="#FFD65D" points="198.415,107.63 199.322,152.889 154.7,145.021 153.793,99.762 "/> - </g> - <g> - <polygon fill="#7684CA" points="198.415,107.63 153.793,99.762 176.322,82.278 220.944,90.146 "/> - </g> - <g> - <polygon fill="#7A88CC" points="199.322,152.889 198.415,107.63 220.944,90.146 221.852,135.405 "/> - </g> - <g> - <polygon fill="#FFDC72" points="153.793,99.762 176.322,82.278 220.944,90.146 198.415,107.63 "/> - </g> - <g> - <polygon fill="#FFDD77" points="198.415,107.63 220.944,90.146 221.852,135.405 199.322,152.889 "/> - </g> - <g> - <polygon fill="#7684CA" points="153.324,98.76 175.854,81.276 221.376,89.302 198.847,106.787 "/> - </g> - <g> - <polygon fill="#7A88CC" points="199.306,107.332 221.835,89.847 222.761,136.029 200.231,153.513 "/> - </g> - <g> - <polygon fill="#7684CA" points="198.847,106.787 221.376,89.302 221.825,89.382 199.296,106.867 "/> - </g> - <g> - <polygon fill="#7A88CC" points="199.296,106.867 221.825,89.382 221.835,89.847 199.306,107.332 "/> - </g> - <g> - <path fill="#6272C3" d="M198.847,106.787l0.449,0.08l0.01,0.465l0.926,46.182l0.009,0.457l-0.449-0.079l-45.522-8.027 - l-0.442-0.077l-0.009-0.458l-0.926-46.181l-0.01-0.466l0.442,0.078L198.847,106.787z M199.322,152.889l-0.907-45.259 - l-44.622-7.868l0.907,45.259L199.322,152.889"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <polygon fill="#628CBE" points="169.832,81.066 170.739,126.316 126.109,118.447 125.202,73.197 "/> - </g> - <g> - <polygon fill="#7A88CC" points="102.706,136.697 102.697,136.24 125.227,118.755 125.236,119.212 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.156,136.776 102.706,136.697 125.236,119.212 125.686,119.292 "/> - </g> - <g> - <path fill="#6272C3" d="M170.256,80.221l0.45,0.079l0.009,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.449-0.079l-0.01-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L170.256,80.221z M170.739,126.316l-0.907-45.25 - l-44.63-7.869l0.907,45.25L170.739,126.316"/> - </g> - <g> - <polygon fill="#7A88CC" points="102.697,136.24 101.771,90.058 124.301,72.574 125.227,118.755 "/> - </g> - <g> - <polygon fill="#7A88CC" points="101.771,90.058 101.763,89.601 124.292,72.117 124.301,72.574 "/> - </g> - <g> - <polygon fill="#7A88CC" points="102.673,90.681 125.202,73.197 126.109,118.447 103.579,135.931 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="103.58,135.931 102.673,90.681 125.202,73.197 126.109,118.447 "/> - </g> - <g> - <polygon fill="#7684CA" points="101.763,89.601 124.292,72.117 124.741,72.196 102.212,89.68 "/> - </g> - <g> - <polygon fill="#7684CA" points="148.671,144.801 103.156,136.776 125.686,119.292 171.2,127.317 "/> - </g> - <g> - <polygon fill="#7684CA" points="103.579,135.931 126.109,118.447 170.739,126.316 148.21,143.8 "/> - </g> - <g> - <polygon fill="#769AC7" points="148.21,143.8 103.58,135.931 126.109,118.447 170.739,126.316 "/> - </g> - <g> - <polygon fill="#7684CA" points="149.12,144.88 148.671,144.801 171.2,127.317 171.649,127.396 "/> - </g> - <g> - <polygon fill="#7A88CC" points="149.111,144.423 171.641,126.939 171.649,127.396 149.12,144.88 "/> - </g> - <g> - <polygon fill="#628CBE" points="147.303,98.55 148.21,143.8 103.58,135.931 102.673,90.681 "/> - </g> - <g> - <polygon fill="#7684CA" points="147.303,98.55 102.673,90.681 125.202,73.197 169.832,81.066 "/> - </g> - <g> - <polygon fill="#7A88CC" points="148.21,143.8 147.303,98.55 169.832,81.066 170.739,126.316 "/> - </g> - <g> - <polygon fill="#769AC7" points="102.673,90.681 125.202,73.197 169.832,81.066 147.303,98.55 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="147.303,98.55 169.832,81.066 170.739,126.316 148.21,143.8 "/> - </g> - <g> - <polygon fill="#7684CA" points="102.212,89.68 124.741,72.196 170.256,80.221 147.727,97.706 "/> - </g> - <g> - <polygon fill="#7A88CC" points="148.186,98.242 170.715,80.757 171.641,126.939 149.111,144.423 "/> - </g> - <g> - <polygon fill="#7684CA" points="147.727,97.706 170.256,80.221 170.706,80.3 148.176,97.785 "/> - </g> - <g> - <polygon fill="#7A88CC" points="148.176,97.785 170.706,80.3 170.715,80.757 148.186,98.242 "/> - </g> - <g> - <path fill="#6272C3" d="M147.727,97.706l0.449,0.079l0.01,0.457l0.926,46.182l0.009,0.457l-0.449-0.079l-45.515-8.025 - l-0.45-0.079l-0.009-0.457l-0.926-46.182l-0.009-0.457l0.449,0.079L147.727,97.706z M148.21,143.8l-0.907-45.25l-44.63-7.869 - l0.906,45.25L148.21,143.8"/> - </g> - </g> - </g> - <g opacity="0.7"> - <g enable-background="new "> - <g> - <path fill="#6272C3" d="M118.875,70.861l0.45,0.08l0.009,0.465l0.926,46.182l0.009,0.457l-0.449-0.079l-45.522-8.027 - l-0.441-0.077l-0.01-0.458L72.92,63.222l-0.009-0.466l0.441,0.078L118.875,70.861z M119.358,116.964l-0.906-45.25l-44.631-7.87 - l0.907,45.251L119.358,116.964"/> - </g> - <g> - <polygon fill="#628CBE" points="118.452,71.714 119.358,116.964 74.729,109.095 73.821,63.844 "/> - </g> - <g> - <polygon fill="#7A88CC" points="51.325,127.345 51.316,126.887 73.846,109.403 73.855,109.861 "/> - </g> - <g> - <polygon fill="#7684CA" points="51.768,127.422 51.325,127.345 73.855,109.861 74.297,109.938 "/> - </g> - <g> - <polygon fill="#7A88CC" points="51.316,126.887 50.391,80.707 72.92,63.222 73.846,109.403 "/> - </g> - <g> - <polygon fill="#7A88CC" points="50.391,80.707 50.382,80.241 72.911,62.756 72.92,63.222 "/> - </g> - <g> - <polygon fill="#7A88CC" points="51.292,81.329 73.821,63.844 74.729,109.095 52.199,126.58 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="52.199,126.58 51.292,81.329 73.821,63.844 74.729,109.095 "/> - </g> - <g> - <polygon fill="#7684CA" points="50.382,80.241 72.911,62.756 73.353,62.834 50.823,80.319 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.29,135.45 51.768,127.422 74.297,109.938 119.819,117.965 "/> - </g> - <g> - <polygon fill="#7684CA" points="52.199,126.58 74.729,109.095 119.358,116.964 96.829,134.449 "/> - </g> - <g> - <polygon fill="#769AC7" points="96.829,134.449 52.199,126.58 74.729,109.095 119.358,116.964 "/> - </g> - <g> - <polygon fill="#7684CA" points="97.739,135.529 97.29,135.45 119.819,117.965 120.269,118.044 "/> - </g> - <g> - <polygon fill="#7A88CC" points="97.73,135.072 120.26,117.587 120.269,118.044 97.739,135.529 "/> - </g> - <g> - <polygon fill="#7684CA" points="95.922,89.199 51.292,81.329 73.821,63.844 118.452,71.714 "/> - </g> - <g> - <polygon fill="#628CBE" points="95.922,89.199 96.829,134.449 52.199,126.58 51.292,81.329 "/> - </g> - <g> - <polygon fill="#769AC7" points="51.292,81.329 73.821,63.844 118.452,71.714 95.922,89.199 "/> - </g> - <g> - <polygon fill="#7A88CC" points="96.829,134.449 95.922,89.199 118.452,71.714 119.358,116.964 "/> - </g> - <g> - <polygon fill="#7A9EC8" points="95.922,89.199 118.452,71.714 119.358,116.964 96.829,134.449 "/> - </g> - <g> - <polygon fill="#7684CA" points="50.823,80.319 73.353,62.834 118.875,70.861 96.346,88.345 "/> - </g> - <g> - <polygon fill="#7A88CC" points="96.805,88.89 119.334,71.406 120.26,117.587 97.73,135.072 "/> - </g> - <g> - <polygon fill="#7684CA" points="96.346,88.345 118.875,70.861 119.325,70.941 96.795,88.425 "/> - </g> - <g> - <polygon fill="#7A88CC" points="96.795,88.425 119.325,70.941 119.334,71.406 96.805,88.89 "/> - </g> - <g> - <path fill="#6272C3" d="M96.346,88.345l0.449,0.08l0.01,0.465l0.926,46.182l0.009,0.457l-0.449-0.079l-45.522-8.027 - l-0.442-0.077l-0.009-0.458l-0.926-46.181l-0.009-0.466l0.441,0.078L96.346,88.345z M96.829,134.449l-0.907-45.25l-44.63-7.87 - l0.907,45.251L96.829,134.449"/> - </g> - </g> - </g> - </g> -</switch> -<i:pgf id="adobe_illustrator_pgf"> - <![CDATA[ - eJzsveuOJMeVJvi/gHqH2B8NiNhRjLv5XVg04B7hodWi1U2I6p5eNAZESSxJNVMXbrHYPZqn33O3 -Y+bmEZkZKTZFVRgzmRUZ6e52O3Yu3/nO3/0fX3718/mbD797/fPmWB1evvi7vzt9fP3q04ePvzjQ -24dfvX37/XefPuJbP/vNF4c6HCv81Pyr8Wv55L+8/vjdmw/vf0G/O9b42wv+/c9++/HVv7/57vAv -r77/458+fXH42fr+058+4M//5fCr978/foGf/O2bT29fw2fff//u2z+//fDHD29+/+H98dWbL+xR -4NLnV5/gM91/Df1/DVU1HoZfdN3hy1/jR5YP37//5s37Py4f/tcvDmHoDqHqD13fHrq6xd//329+ -8/q7/EPHvu4DfvJY9W0HH2+Odd028DfNMdQt/eH5w++/f/f6/acvP374/evvvjt9ePvh43e/OJz+ -/Or94dev/gi/eXX4f1+/ffvhP+DT86+6ry9v3r6Gfr979ekw0hjNv6rD18v3b95+84/fv/vdaxiR -UHX0fvM1Xe6fv4PrwCXxZ3p/+PpX7+Ctr15/+gRPC3ejof7NL5cTTMGHd/RBeJPaz/7tN6//+IZm -Bkbov38hn/SPSx899mFouxp+GIa+HerDz3759sPvXr09/Pr1N28+vf748dX719Cl5e33r+ka/ydd -PX7sy+8/vk5/W+Pd/W9/+fH16/fy65ofzv/6N6+/ib88ttNYNZP7zFf/3/evvvuTfcJfncf3Cxmz -375+9+1bWAk0i21VHWGypwm+u5/1ozAX9LG6P/SHYYTlMLXyuzixr//9zev/+MXhHz+8fy1zOH/8 -9NWb/w1zMlbVoa8qefs33799/fGf37/5BFMS6L2JZ/DXH755/RbuEv/88vYVTZyMon2XT/z21cc/ -vv4ES/LD2+8/0aYZ7S6wSv7h1Z9f4yqr5Sb/9O3r97/98C/0mD+v+waeCUbo2PSHumvHwwg/hJ5u -0h7qWm/Hg6gPhdfAK+i1BxzpL2GZ/dPHN3988/4X+nDD17/8+OabuPaGcBj5G133OLqvSb/kOaHX -nz69fq8PDuv+9Gu3iqvjr7/Cu67vvzl9eIeD/x3tXVjA72HBwraX38Z/0O/gEt9/K92gN76Gufry -45v3eOGXL/6Rfzd+/eXb7+GXv/z44ftvf/X+Dx9evvgZy64vX336E6zd1++/+Q7ED7/H/zzwn8C7 -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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - H93O1VWS39bXgOvC3Rb4NsoT420BP2FeEF66LeCVYV9Zu/LSbSF0xNsGXKfxDuFtHx+DnMutg9mW -24Dr0l734DqoE+eNj3zc7nAuzuvKPbcJXvnsnSPLId8+1xbbQl7ndl2F6yb+5ra8r28ifte6urOr -K5cX4LZl183dd+dzVknlnNuJuPPY926hb5PUdYPO3hc5hO4muorEBam0Fzv0CExxleTcy18Kd1Cf -DQt++jHRpKEEFyat5K//mL/KL//xqweF93XhfasS8Y5h4Ftmxos18h2j5dbpRRn/ex68t9nxH3TB -s1SGAyrLFXX/PZGILNtgonLjNPyXHL6jKW4kkMp07JA32OiK1+3QcbfD/U0t/2YOeM9s8E3z481O -edPCZdXMi1XMxQVKUUVULxkWfHW1vPxz40XWQzYXGa5CcEEmQ16mQ+1Xp/CDwfNqDrwZ8N4z9H3T -jnA1OFwME7pMOkfh+V+qaABYLGP5ejZXe9Z3HOE/Xg/CJiXflJ+bknTVHN9RMb9p/LoZya5alx5C -lbEb35ZEH3pxzpgCZdYSQmB0pUtTOpZ3P8JNmLtj5r6fvd24dVOqrurXVS18V4H8tm3sakW7qnTn -Ecx8bDTYROxEWf/YOf1c1qgkKav3skSqVBUOIzvo96O4r9rViHazM12NPe+ahb6tn9412ZvO+/fH -9I3vd1Hr8Kcy6qbBKnrT6eKpOa5wA6I0lghQtVb4fVKpwfPO7Cd0s2ndpNWbVHtTIt7TNr5pK7nZ -VK6yvK7bDG0Sa1EXPrQSXREB4TigpjDbTbgtupqfTsniVCM2d+a3rQdXO8O7FolvqyZXJeaq7vz9 -hwSxD/Cy06Lwup2gde7vav/fVgjuqsN7SsZuILpZPmiVPhEkUm5BIuXXLx7PAvZC/0VBVLywwgD+ -w6PfD4WKnJhfN6Bg1wP6DbCg+HrDe1FWkp1BYt0cUSX2kEIsXyRPR5ZHy4r9knunpz+8swELMI7j -4LS3Pj58A6Yff71hXBSPDxx1zpjyl0EqcBvQaPE4SgVuIzpxrsP82FIqxn4Z5FiUx/Fcgf0yxn4O -Md12Ld12Ld62Ld727cT5hH0rt30rt33Lt33Lt307cX5+38pl38pt3/Jt3/Jl38pb+1Zv+1Zv+1Zu -+1Zu+3bifMK+9du+9du+tdu+tdu+nTg/v2/9sm/9tm/ttm/tsm/9rX3DE8gXNumufPLKKK+c8sS6 -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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - yboPHvvF3+vbfroHMVYqsUZAtEtay3qcvcOO6mhattcKfB7mVIxcKooMqUSwfUWZ/ZIQVX5xsTSI -rU6FzU+uaW/HaqlRW6Xffvz3Mt1OYqAK4aPIByQ9il2shuQpAUgczcltzqNQkVO68EgSR1PIb+qK -xbWLnym+PPmlRle093QLISWNVl4ZdUS6ZUBRs5OyIR2aOoXNANmOBrm4dCw2D8l2TbtDEpnbFS1i -uolzBHyNqE7JdRWIukT/8qO/fzgx+DGv9W8qH8q3m96DqIVi94ajvrWzsabpmtHp+a4nZn2EhqKr -fv4Lc/DkW0oMygjpOdY0gnPu6Hv3xdYE88atBQOMg7A/i2E2OEsCRYPkVsvL3eOoLQQrcNRbEnNt -pg/vro5rl3VUoeTnoATmirr4RP3vO5gV5PESlfIwswve10NHZ4bc1qSxp+zjjUEd9qHmgUld/8Kn -lyPFNLDXRPv5TgPqvyJjp3tihV5aG/gsRsCTZMgL9wRSiemnkUHb2VdIKZ6nv3cUuUi6bvTRUu9W -ICT4slOVQuwDZ8Ais8weybVHtUVE36Jrd/LOFn8tlYFULhbASyjI75EUxLEbDeADdLcnTGErGtjf -SgI472mqVbaSsGiGHeX+Cbg8rpCLs/U4xMJF6c4I1PTMoYclOrQNybXuyU/5OX7ecJBkZB6GNaoC -W0j+sZjAbSTitJs3vTg31+PVu6MfsyeISuTuXAl1S3kzDsTpNk80yABRHJGBGTck+us9U0yD7L/+ -Wv/kEm62MuJQ95FX8ukAghCF3YPX7zU1vbD7G11PA7iqt3wSnvzfQlCGcwdR2V7AffmK5gDxAQ6I -IC+Df/umtCCoOLlP5M99GV2CryOXTF7J4wYedrPhSX3dleaQQv9FKVUREF4MkMUNpARxgALh58ng -ByhyznkbkszTlHH4SCcicvLN80w7ONWubWpmHJ0QiPTBS8ch0hgUed5Q7JLXjM1cpFvnidfpAxyk -mfKbO8kX0nyuGT1f9+njYVJe1pbXK7ZroPuG4tLeB2U4mUKQie2TXh0alPdBwVuWi3PUbMH8IfBN -SRcdgfuXg2L2hQpqXvQOx4l4hCNo45E7SQc/tyKT7MjFTYHX6+mZmMMan/INcnENKXEXwD62nYbR -R7gpWwuszb4J5+0Ut2KryLbd9DUIEpyenjctbmdb9NyKxe/7ggVTj7cdA4zKAZyLcWf0JepyW9EJ -cC6iOLIv4EkaZHRc3AfOOMfDqA27b6ojysAVR7Hm/99hd8Lu691itDu4V3eZwdlZLftCCNbLetd7 -BezHDhk39NRKSt1OE5mCMhnzIpJ1X+aiE1Ye1yFN+xF/+vmYPyqYl9wb//WD8zFp1Z0CUKjYvQp+ -qPSlcyq8r8sYKfg+5GImJrCh689tZn0MDXCKFVBtPQR+/hSkiOQr9+3/+IH2tB7EyJWTUDOi9ctw -c4QAsFcC4BJ8dwlhPNkVSTnVG45jydXKJR3FB3kwqpNs6nc/0hR2PZ+2f5EbZr1mVCoF+yKRx3Cy -inG5yK2jG/BxsfOp6g4QRpYzUglGHs8sLGecyWeGqpO75mI77FaLnUmmdghpdwSUzOsgYYoYR9Td -O15wi3KGqr6TRSl0mxOqwhOLYhxAVaSdSRTpSVEFBQh56kS0AUjzeD3haSlKwkd4vLZJLMFx5fBm -GFM/LAveT1AKJxzX4+JRSeHl2OXTLNP5/E0/goGJpxtH3cMc5zjFp3suDzjP8OnpQEr51YfdS6Fi -d1baIxEKz79TMULk2Wbk2WSEgy8SlTC5uLMLzgNeMV4mhkLBUYTrT+StDEsgEz730x5/esq1J1E6 -mcNEOswZew8zmScQCirfCtBB0qQ9w+rpT/mT04l4DSCtXMlFkbr2lDRi8ou0Y/Ew3jhETSqpShT3 -GfBK52HHxwBaJlVWY1UT61IYLADeSLlu7frGsYgrITt0Y1kvozmx1ceTVnurCar2xIXqHGBRIo6X -K7l0enjzJ3ubumf52bOT74V+WGWyJ7rdk0XSFn1ZFGdCUOntwRecelKU4pH8VAfjBgAbpXKlfcS9 -acKCAwe8C1qnZ6fG5uu+m5LxA8iujCsc4RKmvLwcIi1PJDFSjL0ZUfp2KLyPcNjW5RN+hz1Qct25 -2881rKVdbDGpfm90udMe6ZRP9oOH4iH6heOj1DRM2ImsRquTZmDZFuCQK3XRoHJwR3zCl/oYJ/d3 -y+9MdGFBLVfeMHvfXKfAUoRsPmEuRwYmxguIYn22fPsOEtaOAnTLgMkDDw6f23aWh/Q0o9FEVwpJ -qAvZFc4lfOeQv1v7PQ4aubzOoVlRNBdqYM03DajoQepHpjBI/N8a57UqL2R7o2L/8RFCi0/bNcBs -14ZRpGinIykF0tGtUgTo65yc76gAxIPLVT9XNGmmLkSxT9jTz0WXHPptzjb205c5SL8Xfg5wDyEE -aOiI004Q32j8RURPlipVkKf5ctU3EnUu71hQ1QT0mSTArRJSoNdEo2I3LixkMHuY4g0VI8jyJhNr -FBXBGb2MrdgmNmIDneiYTDSQujODzDgx+6r25wLbPfbKbOSX7iI/ZkzO1cQ5LY8NG+Bgv8GTUc9Q -MybXaBjvgT/hy2U82P25Gb/q0YvOFORpTzfqIZtb6Duclp/GaHR1D5RYQKJ7qtES25USxNN1nBe9 -YYAgSdBSOcKNeN3ZTZ/I5yVUndgW2yKzpwa7WJ0c2T8ce009hcvoNumBON15JaKst096yH5GSYbC -D+ptzT5MfaAJ5bAEuDfB/qIz48aVVJaSBp6kjByr5HI5TOxot7baHeCyg/R/AxciucJ585Nkoq7x -94BZkWxjQUseuvclXzmxfaaGDE29UkXHocLbhbjN6EtGUsJkjkn4LEkrV/ADMR3W2Xq3ybwnCfk2 -8cgGq6Y72FG2xalilMZ68D2vfVlt9ObKbcDcKE16T8c2996rpodomG1l3O9WpJ1g0QLJ9pqk2tiJ -uX0Fz+K2RAmLzR7Mnk18qBSmXdZFnkTDwI/dGANHRm6FFPq2AvB3rYMnlS53DKViXrBiki7sGgRY -ScuGHdVipHqXH+kCEljLn1ra8RXXAN6b7nR1goComEIPZMbjQggVnZpQGOge2lY5fYd9OVmr3LRj -CsR4QAzu8cRqVu83Y9S6VgiwbFJk/v/svQmYXFd5IGpZGNuyZWMEBmMM5UVGXrp1z7n3LNdis9oY -DO0ltiEOhCjtVstq3IvS6rYteDPJTF4Wv3nJfLz32CYbYUjiTBIyk2TCNyQh+/bxZRIgIcaQ8DHJ -JCEmM3gJhnh759/OPbfqVlf1IulKrnJbXf3XqXPP+u+LVck1kqLg4GLCgXkusagEsHZy2EQUtZko -ipFZJkptwKXP8XnIK+sDwJ2XDFdsvzAlJXHnp5GuwZSkk0cg5k6LPUgBYxctlIZ5LT5p7MLko0Dh -YsI8hmey8IqFxsQ1qd5DTJiPfCn34GLBcEdW5b7dArqRzUQvPe7BxmD9Kpt6AFZER46J4RrJ8fBx -B3m0H7vIRBhNrBIvJLmegWJC7kTFWpgqCsaRy0iVUzaXOoDikFCUopKGMyLu3FyAx0vyeOq4KEUc -RvTAeYKr5A9OCo4XnPyARpCEAfhIv6vM7eCR7uQ8ST0CSElcxDJsVdl4gJdyUyRZIKQkZldEjuRn -oHOSuavKfZZ41jvy4BpDh31REVfHFFNJm3i1Y8wYRBIYuUFodySgjtUWxDINwLiTpvLWg56N9Ix1 -T8cooa2JGeIkFsaQiZJ68Ek8BZaAZXAmaXJNUR3pmFFbnO24ZiH3UMSoW1eF42DxIjmQkn1XpeOq -CFmRJXigiPFPEpbtqIYHNZTiSY7MMbEDyRMAecSI8AJQS6U84YuTeC+X6jsg272W1JGSJ6zIYmyk -j+n9iizZCVdF+EJ4mZGScjEQK4vuy66KSgLvBEHHaHOnHvIyrYelJUYuqevDeqO8jFld0rRpOQZw -StsqXVxeGSgc2fvGMNhQRCcXBQTI0WqF/ajQAISqOrkqUpI0T4tIIZs3huHiVf2cJPAMwvE56Mum -BaDY7xGYfzYe6iqw21IMC/WgqtLCLkaUKgwBkpvJJT4gP4STIhZJFjJVxmzaLlZILGOImYt+uNhQ -MuklrDZWbpNqoOxFCwkxSjlkIkcAUMdEekm1aa5KSESQzcPKitcKTjiTEtQcMoaJGat0JiYyxDZG -TgDQS1Up4bWgBx8xbJZWvJbq3ZaUGpMRXkjWR7S2RLjOYvEOyWhiJHcVbKfUuzRiqEgyBY5Rtj2u -2WIjswMJoiktHWwSx3xBXiEnhTiTIBKswCsHO9Z5NlHXZaOqF1OLxh5UVUNVR02Diw4lmQJ2nZfI -kedImqKKsQb2APCqDJaI+b2Jm96WpGvTiRZCJ+nakKOhr3B+ItAfFRKZX3BW1zKLqn6f+vGgRleJ -usDGlFPaSzZ+ueO9Y5iQ4UG5cxu9vAG9zkd4lUiZ3e5RyxzdrHl4ADTit5NVll7oofIrF/V3VBKX -lc8ZAK24QGaV74iPkfxov7Wi6S5LUXlyzoneSVTz0yJ/4OicZFwEuBMjUByITtw2pPaI16LSQOV8 -xLK9PcdccWgHih5WoPucF7iLnsnOiCmoKjmBQVsELHNtBRhpC8Cjh61EnvlMohPEUaNxCBPJ6Fiu -E8eZODrmecroxAlaheiZnHMJMg9uG2I5TmIRySwmtg02pfpYTB49WcQC1zWCODhwfvDinIybMR/h -SgbHKfgdluWJXitOgOyDSabbqECoLKpRRHYU08I+SIX4TnQNoBpb6lIjaScBqJ3YpLOoQSizCGR+ -G3x9TOViHVkOUDdEhxoj/lRcQ5vdjbx4EJUxzAyrt0w0DazKiuiTkipJ0vzCp2VkvBWZGuAxFQVq -HSb79BMfAeUHjCTQKKuMppB0oYwhqCrmUaj85yTHhBUNaVmLEMa6BmLuzpgfzdNQXrEs9g4h7pgu -kwBD5O3nI7yMOTcgLCPW8fNeVISoUprs08/kpmVeNBvLvBjj9SVdcjgXicc9wie74FDH08VQBeyi -AZR8u0q1mJmCci1mUKPMJm8g26IuoUxW7xvOvIiOBkhUbJZWOECjNaJYW/mygPsABZ/bKp4GtJ9U -PtxmaR5jgFPZP5vFAoyVTwFoKFjtHLhVCsq0OlV+gsWZLO+Wk9/TeDnzvS2IGyUEwVZkDI+odGig -nkIiC1odZkxdjEECzUnmxDmjaxmmqRAEJKiC9AZw4EMPOb6x4I4Nq+sxmSW8kQ895bus8loqg1QQ -RJBI6cooo2OKiDwmeyUlCqaI4GSviv6WPK/UrrQ4KUgEwRFuZfQFByBnFwEgseAgsVWRTugTprkH -cVMH+kD6RhD5qlyz9fFPyKps6Mw5SRprCvJTmxd7AudbNEacfsFCQI7AGMVKHCoo/SmCAONdo8wO -CXupeLnJYyVa6IGBOtJQIzozjPv2iaUjI9eSooxZyIBlJus+ImkbbUDkHw7KiqpaPA== - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - JLZHXPcx7534Aka/bs4UChKJTXKPBcTCtmfOTQmVLdjOVpV2BAbSkVc2Z5+dEHzNRlvrKKfFGAr3 -HBFtq0p1QMCYQzU0a8ZrUGUYhT8g3JxkrEKCjtMNEbrMiZyCPs5Es5ONZUjBA5tVduCKSZFQzscA -NLSiUTKbrNJOSA1TFRPRgMMl0WJMJMxyFBinMpYV0jKqgAKo9FERoyvBz5oT7+QU9MZk09Pjc8pl -FscgaYly4lwJGCQkS0DJqQ/GTM7IBvdSVR0Y73kIMaVWlQq6iMVfYLqKOKKC7BVxEuh3So3Zbwv2 -X/Oz2DILWas40RsnDuPve3ERAcmGi1tJHASxXxw+BwwJJxZTlCA5yq4lxS54HdNDgEdEQRmBGDeu -NgY22tMqaCVbKQlN4DbFHqQEsCF8EWfB/Aukb+QMGjBgK1nI0Bw0hkI1F3KF3D9VKndTlVN2Vc1W -0H9LOkDH1MmYcUmIgvEq0/J9RfZLiNrzXHbUxvSQjmRMAkpNKZ+a9A074yFcsSEJXT0kl6TUFYA0 -XV5Slqa5zUwsWV+lKgJXj6zkdC9SzFTBIpS0jJmrSkeCPV1yhUiAfIFOgFJAypaSt6qZF29i3BtZ -/EZ5YJPi390mxL+LqjArhRGIAmgFAr0ZVK1LQtwbQPTFKuDdsZnDCisa/ZnKGLoBQ40x2Bh5g5oD -uD4xBtvkmqNEtIupWthdxGcxwy2m2Si5A9SyTkgPgZPmxmhKnow9e4kwFFIuOS7ocXwQS9ZnJKqw -MhqP6sOqvixGop55TcRsIuCMQ2TfkdV5XuAmM0UCn+yFo3Ixwh0VzILjyn5FIP5ZL34rXLGk94nV -YArJmFpwTrx5gYc/uB9JYdrbmICl93kyvMjOyjFi/irqN7hYsXQS4RhoWI18YPuMWPnCxQp4SBIi -p1eayHAbnx7oSgNDGZdBBs2ZpyoggSlrjcXT0IO6lJyEanmtEE1HgSTZskJyvULPPobFqwzVqDIM -Yk2ZSsvjIh8bcHqe9MxAZuWT9U+BqOKsBJLSCncpAdrptjJfVptE2kHPbs+RKMoXWOF/cI1BAs0T -DFRfc0ex5PO9u1k/FVmWl72HvKefSZaH14Bm6vhxjKIBOe18QT4J8wLXljJEFrEcBdhzFeWFNWRX -ISA7K9GNi2HGRczJamKJdwjXKyQnGesKobYVTQxgVTnOGlxXuXQtHR1CBtSrlQo+gAlskeS2cXQH -IF+C1lIYqsglY5wMwSZ56NIhpG3RwhUz/7IPvDxwjCJJjVNdT3OxGkV9aFVjnkcsImW04faYTK7n -iQUpkml+kii9IDeixh2dTpIQ8YqCGx1oIOJmO43TNC7qxrDWnWMXGsmuBpmBtWU3uqziTh3WzWUx -BR0sCciZgY2vJK14hoBLrAJNjagjGBxrlnENClOSFSzCSyWhvEkVQCNhrIZTTsTV4wMCHG8eq3jX -2is2pgDvQpQNcqqKL0HP0k0MjwIwYtayrzCa7eK6i2Mkd0pAjrWsPx7ToCEwSRhZbTZ6IXM1xcbt -TJ+VpJ4Hbw4jrpWO0x/CgCmHo6lSiiez8EkdieRpPsqSsIYsZENq+6riHe+xT0tsQA9OIqiTxg27 -0LOQE2vHgd1TwUzi1YYox2HyIqZh0cM8PVT15exajAqMU4zVFCVpR394co4xr3feAJbdrh/jnukw -aejB9op01CxrqZggRFH0bh27gJsxRT0bMlczkEpeCQmYkB6MLcqECoyRW0BedKNJTfXjejBiECpL -gYtXM/Tgozaf7aUY4yDYNx1D1ZhnMRl7LhS3j9VjUenFA9ZsdHXgKVfyGNCU2DO2ROMDOTc0Y1/J -Ad67whX2zaREA+SJAeX9vMALR7qrgupWTHZtSoAXkBIuVhQuy4LtuuIE7yjHEwOlPi3q6iX9UFKK -L+25/kTeRFOQD0OEBynTdHUedwGGF3PKZFR/RoYcH+iUz3unksJxgA0DwQdOdp1fyErtqxKLmkT0 -Hng1xjzmr6p1koPna+PmRMFAgfrcs1IIFX+0byq6u4MJmrO2KMhepFlTlHPuS/CIKzPWhyQZxpPG -OvplgE+dKoqeHmQM3DL2II111GLVuq2iFDEjfdWQzdLgYJtAJ6VbTqsB6jUvWWo5NMZXPsDKS1AJ -qOeT2AvwIiQ0oaLXiwIPDklSo0yMbpWWWerlrZwUCITcM5xPS0FOb5FwoxOiFVYVkvXESCsrWBvy -+mRZkou2LDwnfhCjIQSmECMFpqiilKAqCYkqyRrDPYM2P/pSKiPBVnwp025B245qJWnJPUANZMOP -Q29LAnKUkTxuDB35faYZmFSsU+xpwlmLJKbJxZWUir0JUKVVZrNYwRG0/FxZKytj8Z/kNGHKNFZz -JnmoVBYLGOSUBXVS4CxEgcdGHgN3WO6WO8Qty5J7wGpksWcW51GyzWUlUEFJyk7OwQGhP+wdUlBh -Eu6hglekS0EuEiY7nOQc1pxrhjDNiRuUK1E/SsFeVeW8r2hRDVhWLuQBbrTtkn6qfQcgu1EFIGNu -kDFcTFisIAeu50TP7FwC6ZEpugVgXPxCWeHgMK17tD9BhFopGY8xDdak3AzxcbHkRBPhzHh6tiJG -OEcKSD/80EwUseKJHoBSacqmxdeTHliMorusCsVzkew2EPlCwi9POiKDqi3u7KTApXnFdSgfi2gk -ixQrv3atcoXADJks6oiK2YAxCfUpe86azmIVQYZHHOrqZ7O7E5HYeglM5BmgpAjLVTWJDWM2HHPH -wrXXgMgd14E1iQ0jiMqiztWiD4PVCSM5hvafspQ4K2wZ7UK+EF4Xe56UnsMUVTLmnp5ZorBFTHxU -k2psIR51xklEks2ltJ20JaBEZtY70FQQO/LP1JbDaAzHK9XHmrLxtcXhFGsBxjmpgfkuqZIbmPep -PFXX6kLyEUmNhWXiCMiFr0QEa9zhyHiAoYC3rqQQyHlR/zsW2cqYRApsAhwlBLmYxWOY2VZw8S0r -KwpYjz0HouWcQtMYKWJsMQpPYMQkQyCaqpwQjSRKAocS9g9DVk1zelSJKwYLqaNQnpyidqIRhHXS -cLxYuWhi6egkGA4CezicSKWBSqgniEldwBd5UuCWNJRgeYi8LWwkcQk2q0q/uCi7luR017js6Y6w -fttyyZd5gWNdpwo+KXDW3UMImxjdLCWJRaDV4gKakXOD5dIRcZkUHUTrxLcHTEUOkZ51kTzWnoRO -FRPdI6YOGgbGFujqWNWeJeOyqY9HMi5LGVsmuw+GjaFmaBBHQQniz9hWTIw6u9YkdZ1NFRpXUJWm -MbRjMbtjOUiVgIot2yZ1Vgb3X7LLgncqVyU0WQx3q7LaGa43T0F0VbxNAgdLfRRXTCyxa6uKickw -qixtBhxMHBvYS2OTnlmChySINhbOYU0wSHEcyAohtORy7SijIzuhlbQNlDuNC6Zj5R3UT4NwzQaA -alwOS8FWq8PMFejsHOXaMVp4e25LME81r11ObufcQS51FCHmB/SCk7Jxgdjm1B7dosbYIoiSpqvc -vSBvKXFdpDZ0PVhF4NxzPD/gZ8bmHRQkKeUBDIgkjASoycmzG+EJPB5XRZWu0tUsRGisAxmPQcrR -yjqRjBll394h61h0LumDnazqi6EoDje6S1oWB6J7ftKritnek+HSQYnfDxw1fz8RrZM+uJB0jXik -J7CaWlazPkf8AxEeQBTiYpZKInvR8zLiGtwfCmrlkPsEiZUY9V9HSiUxbj04jKtNx44ZUdsSNfkM -BiOY41uKgdgRjvUD6VaL7z2o0HMOEBFOvxfNVxQgUkQseZqZiibz5LGyp3LJCYulOiRDnmhXkBmV -FNbgNGqlLAty1NNyr0TuTgp4KcJLyJOLUyCkiM7FZQGrb0ekI6VkPOVDrqEHKFGbMabGqtxcACHx -tofsZIVUvNBcx7PC4JC/m0XI6tiVWQ1rxGMeresVvyBm4+q8EM2BrN6VO3hFn3yV8xpSl5Iwjtn4 -+XD1bNIalPXJLeW7MN8LR11SnIbgnP5w7GeyT/+bYceD+mKRUUBl5HwvXOojgcExchAxCthJNR3r -yOI30dCDFLDywsomPAj4p5LCpM7EiNU1ZSxqvSo2GPVOIt67IqcS0xUnNS9wLqkDqXHBG3xS4OIO -x/AxDJqy0RcuLgYwHJTSwKUx4IrC1JE3EDUceF3nuKeJk3CO1Zx1fdQEZy8o66mgxRi5pztiDKpx -5U7UoNCti5l6cieXRjDqGMbZiDNdFt3xIIqcgr0hbXCVfxWy4ZOnYf0QApwU3aD45HJP6CLvGLEb -TlQMsT7EIkvPcX3CaUmfGHtmLUIyZsgywvq1asxh4bW2KmnJPWtJzFinI4UWI17CfUGNuEzoiHBf -yaFg/m1CDgU7I4DLu4pVwQFOojqkY+Cq4NAJoVhbFWvqPYbxhOpSNEGO3bNivRCupilGvDGMJ7Kc -xSkBlqKNQvtPrI2jMZu/TuDcAydfL6J5VvuYWTtPXZE1ltTVCS/HtUmwJEBOnbCgreEwCvuDqabG -MFsLRwmhBThPUp/yjYLGJa1cAPLGyijGMN+EJewNaeOrEoo6ppZ1lVs3ALXj/L7iJKwxmAdbWip7 -yT1A2VXJ7ytZeiAPBrPPtsqXZCRTEOTBB3f+6e5ZcLXaSYEzaoB80nwuYCnoXLiqdBYkyaETJD1M -yAKzkhRLfGQSN8Y16zBvvmaYJC92lL87dsCe985JoFwNJsV1a0DjqkxOoOBChwKZc/08VPGcyZl0 -lDmpOn5UOY2cFpKCN3JcwX0iOkdorCyYM5wvTg0odT9qQNykCemh0AXD8wqpw1XiTOqgw+3UB2co -QUHjVVwD/deQvdVxZnYsAT4f74rxKjl+8Q5llKxE2k/26Wfz6v74Tan7gzlOKBV0iQmhUvhkFxwd -UKIUUfYBJd9GN8hb18LX4ITu3U6sZcE1YxUpksMwbwtLtG/3tUvL181OL88uLkwtHelcA7BdY5gL -2zt1RWf3bctLswt3dXbt3Xvt9PTK/K2Ly1PQ+IrO1dB0D/wT5SD2ReZ8eJv7iLW7gCqaO9iuyXUC -1Nc2Shi+SNTaQgXQRFiKkUAK5QGb47gMIyJrQnOQvJxIOWjL2Y+hMk1WVaQBsRHyRJEjxq/BfRYV -OrPa4JLgYtlVMYLnFBKAQPTOp++D+GbERVkc1q2NKnETJSQbEyODtQYUVNH7OqtGJsWTM7E643Az -Id9SVNHFACvIrcYOU2VsCTknSHsCDrEcS5BriWgrqzpLjFrIpQyEEy6YF8iVITxWSl7bLNZIS6qG -Y3wsDbSMghggbnaJK6PPCUSSs4uDp9DiSC3Zog9u4i7aYmRZfUTxYPiLnvI+U0k+OGlciuYvrACj -Kqxg6iV8vIq7MJXmNs/FN0U8FcfI1kScFQyBPQQyHa02msJzaA2hZqbKOJCAyzpBKnkpy6uo6CAB -xcqUUWaxyrrGO17VKYLYbdLageGRtceQDC8vpAQJeO9Oi0ycmGDF395F9qkS1kE/TQ== - ]]> - <![CDATA[ - NbCg/APEn0QVP4d/AMWWuEsnjgtYAISrusdgFWACILaHr1MelWOO0jfSHWNpAkueFFL1i/PsS4Af -3+hcAomwQAc7PpdyoJyvqmbxNmTKxAi3Mp4lHcvVo8AsBn0p7gD2QCP1D1LXXC/ikK80/t5TuCgC -nfhdl+K/BtEnVYZhON2ZOBrIIkK35McL7hJcDNrz0pEHRGXIBrUKR1nkMRIIC06Rv06V6cHHempg -ZiuSMRAoxoc7UcdKgQQAAmaRswxluHkDoPCuHGXPgk3lpo7BlBx8aIR79ZrO6YT0wDwQLFcZJ5tx -7ew8YlUpzk22w9xG93IXYwGKWHaaLrI05ko0WHJYTJW5r2IdXfQiqZx9sVa0RE2JbRVXu2DDeqIT -lKr1CI8LDpkjrXhTccY2b8SaCEAQMnklwZUrZ9u42Fw9p6YjOz77nPgicbFCs8uEEMiSa2+YeO6B -0pXRZZkzLHis+shWZlWZtWAveNltFW5oRB6Cxhxs46NlDSuxRQwPD8ml0rGkZUHLsngRS24NAJLi -EUYDOsDYg7geWFCnyNnJpVRyjKgtyLBIwKoePMApsSbWQZNggLBO3GmMfIWGonCsisYgOBOWoqwe -xoTTRtMlAi0PSzJlXr+pXFojM7TJjzBQrpTdh7AkfQxCYeqFVcQyOXvsRgVkQ8Kg+baD5w8HPW7i -ENfNRBd5goYwKSoHOtkY9AfBpbiXDYz+PZvNcYOMbKKXvo+BEAry2BjxL2FNW6bEKdJztuLNHc9q -UpgB6L6bFhduCR0th77GxhiO0ln6yfZtNx3Cz1RGH972tjdePzsXutq+bXd8D4PcfceNkzct7p+B -97sOIDgZ633zcwvhwzH4B4Q9HGzzx/dMza3Q56qz+4aF5fqny0cO0YdhmZamjvQ+eeb2laU7V+Zm -FqZnjtHz91Qtpg/Ozu1fmqG92F3fxdowqZvdb12YnQ6gVUe5azmd0CrfqAauN3XgSbNwiJZm71xZ -njmMDcMnVbfp9O6cOjxz/dLMd62EUR8Zdp5gEG7hDLvm0j3VhZX5m6eXp+6BLww3T93CSaaz6J7h -4eXZ5emDt8/ODT/FhcXb8EstnGltNt1TXZo5vDK3vJaL2cIZyiSuXr1d99wPzEwszh9aPDy73GbE -uXhoZmlqeXFp2D2aXWjhDlWT6N6EQcOtJnbb4srS9Mwbl6YOHZydbuEcZxeaZjcA97X+buEMBl+s -9Zzs+4amkztbuDD39e728IS/jRNqIPXE1YaBLg9NCBfvfNfM9PLexZWF/aHV3sUBm3xcZlqbVs+V -3T+IGOy69oZ9184dOji1T7VwcjD+7jndO7t/eQBrUs1OZa08njyH7qkdnJm96+DQDExL5yaTGIBn -d183c6CzZySHHis59MDSVBAR5m5anD180kuibURlmy6IFi2c5EgQHQmiI0F0JIiOBNGRIDoSREeC -aHVl1yCItpGxGQmiJ97cTg5B9I1TK4cPz04t7J1bOVYjWA/ZOby8/7qZe2bZln3iSjD1eWyMsb+T -tqxtU0wY+w0xjYitWzg/ZhnXJrTcfODA4ZnlFl+wtR29RZzO3pP1ALb0ZjUJK/uHZsjbiA73NzDk -+4fmyFs5oyPrwA63HZqZXpmbWpoEjiLM4/iwArcszi4sTzJn1lY8NfRxHzNZlrXwgGxEBB0LrHAr -J9Ughr576Enplk7q3UdLh3J4ZenA1PTMbdNTc0PbV9ro/Fafx8aY2cMBBd68MqD5iJ042uzE4eUj -w5/JOSZXY9OLc4tL19x7kJTybZsnz6lnqkx033DfocWFmYU1qCPaOMeeyfSb7sTiwuHlqTVMt8Wz -reayZpZrZEU67lakEYFo/9w2aB9ruyZnKDPZcwBzTC3NLh+cn1lupY12szDI3OzyLVOzgyjfCYpC -TnJDe5sxpO6d2d0DuKbEqtzCKcHoe2Y09Fa1kWG8u2mP8hN6RnnDjAYoRFt+6op1EOMbZ5buOlaE -uOnR8HeL+YDh6UerLTrrMrmdXPvTYuZluN3ZhCcN38s6TQa6lY4mG7IYtHNKDfaC4Vy49s7cMzN3 -28Gp/Yv3nqQRRUUrN2xzHLnaObeTw5HrxsWlQwcX5xbvOtJierdWvcT+2bmpVtoUNksn0Ua1WF9l -xP4TeVZNmPkkc0mrn8Op/bMrQ3uAq/E2Jh6ROayd9x8hwxMQGbYxlGbjyLCNs9oYMhyhwBMABZ5k -Hsd3tlHa2wTk0MZpbQw7tBLfNXlOD69EOWkcjUdpJ47tJm1q2ok2kqdNTzvRRj/XUdqJ9RDsNh7X -TSDYbZzWBgl2K3mQ9Zj8rps9fGhuanpmfmZh+capQy2mcWu8SwNcFU7Uu9TGaZ28d2m9vlftRHlN -bleH1xJR08bDd7g5lObIxMGphYWZudtm5mam16AxvLaFU+ydS/ds71vnbG9t4Wx757JmojYBwTU3 -ToV297WYoK1FaJuXybRtt5rFtTXS6jYqCzaBVrdxWhuk1W2kAE20GgcytHgpdR6zTqfnraq9beH0 -ZaprxpLHIY9NbQRTC7PzU62OCTkwOzc3vFptZubdbVSo0SS6t/9OKD4z9P0YcJOOjx6NZtA9sf2D -3HETxVkbp4Xj757U8uKJrAyE0XfPKLa6CYc+ZEjL0Cmtjss8u+bUPeUgqEzfuLh/6NnOzS7MTLXR -t7yaSA9RWVqcP5FDRGj8PWcVyoKtDPYkSfXVC20kBelEeia5f//s8uw9Q09xaQb1hm2cZZxJg1iy -PLU0vKfh3L1TR9pII+I8BvN862F71pM5sI23eVMzB7aRsG6CjNrGaW1QRm2l2D3KgLhruo04YuNX -qJXTGvkj1Wc05ls4pZE/0tXPMX+k50ZB1lam+xp5JK2HZLfRjr0JJLuN09oYyW4nFzLySEp2qI22 -s024S22c1sl7l9brkdROlLdhj6Q2svUjj6SRR9LII+m47dZmeCRNt1GHuAm0uo3T2iCtbiX7sfke -SWrkkdQaNDmyjzXcwzZqtjYBY7ZxWhvEmK0kAuvRFIwyWW5SpsR2nvJ1H4mTZl/aaZ0f7UuLs7ls -jjvQUXAqWkMVsp0tXN4NZRRtZxLHUSHsihkcoIK7Yd/E4uLc3ra6+G88hapq5QkdpVAdpVDdBML+ -HMgaeHJXPVlbVro2agSHz0m3Ls51KBJ2HR7rfW1cHiZgI0Q4QoQjRLhpiNC2cFYtQoRtXJ4RImwl -IpxZWhwUtznCg63Fg23keNqAB98Ap3rED47Q4AgNPhfQYBv5nfagwTauzomPBk9ij542ulH0ePQc -t0uVbvy+Ni7V6G61+G65Fh6Ylt6tNi7ViX63Tq7MdEsz84uD8vq0KDPd2hylVUft0VlHmfBv1gn/ -7wnvw+894YNOC+ec+EaP8vF172Ur59WYkG9d6esIod066HCfDLnrDh+C7HUtnGf/3HWjLG+NtGOU -5e14zTDN8tY1wUMzU8vXDY9YZxf2zxyYXZhtpS03mc1gpnokArVKBNpIQr25Qef3BA0ZaunEngPF -nNeGOyYW5w8tHp5ttZi3VgPNoA1tmXVmjSGGsmE3rwz4Trswx/DhPi3FHBvLPNJ+w5oeYQ/ihJdm -lw/Ozyy3cpuOBhZpY1D2xtFIy/HkpqCT9nIiDRmN7h5wzhLFXgunBKPvmdHQ29XGK3Z30x4NcINq -+YzyhhkNsLq2/NQV6yDKo4QAm5UQoOVEsu3BzqM42sY5XnvDvltm75uZu2Vu6si+Vp6rBiXJkFab -pART1jFtRKnJRAbcnZFxfmSc3wzjPN4EMM/rbE9bb8XIID8yyLdxlptskD8Bi8mNDPKN9GJkkD9e -MxwZ5EcG+XZt1cggf+JObGSQ7yn8ceDAyuGZSciLFKYxki1HsuUqkzsyMze3eO+eu5ZmZhb2hCs+ -sycQ1dm7FvfcM7s4N7O8Z2lm/57FpamFu9q4ACOhcxWh88Qpy74uoXOO8dvYNNSEaOFUR5LnSPJs -pCYjyfN4zXAkeQ7BPYYlWlie5NSgbeXfZubCc9Yidto2Ks2TWfSglXfPzq+sIfOsb+P84iQGH8N1 -qR9Wlg4EVHrbWiqdtTEVfH0eG1M/7Cfpr4Wz3LgGor1z22Cd8JZqVhrQ0lq47m4hs4VT7J5O891b -E4Jpoz9GbRo9MiXdqonFBaT8J/I8e6ayZvbntkMz04HRXxppz0bas+HEANCVkfaMVWmoRBtpz0ba -s6OA30bas5H2bKQ9G2nPRtqzoznNk157NrzIPTe4RFBrBO6RTrCLhxnpBIdl9lq4TpupFDzMUm07 -I1mfUxH/shVvuO9Q4NbWoG5p5RntmUy/6a5Zu9RG1NU7me7prk89ujQzgB63RTe6NmboZMztcFJn -hjnZyUS75/ecSOgwwiDPiewwc7PLt0zNDiL3JygaaX8mpg0hkhZb2TeWFaaNFsuNZYVpI5O8saww -rZzRKCvMiBifBMS4jfhv49S4xazGBglx28WVUYq29rMXoxRt7T91oxRtm84HrJ18tPGsj9Kznfjp -2QaQj9YYz0fp2dqVnu2kzgHSxkvxXM4B8lxOl3HzgQOHZ9rsrLW2o7eI0wGcsTSzv60b9Zzy+Ng/ -gMepJtXG+r4w+p4ZHTmhZ3RkJG9tNo4a/uKeEPhpXYTk5NqnE8TIOZKNTzjZ+LaDU/sX721z3vKR -0NhGodG28MCMhMaR0NjKCzYSGk+cG7YxodG3cEYbExpbOaOR0DgSGkdC40hoPKpC49BIf0yZnS1c -3wa0PzTWb+mUjvROaSTaDxDt22jea7J53zu7fw3R1EXWxvPJc+ie2sGZwYH7ydzyVs5NJnFiq2Ru -X1m6c2VuZmH6uLCCo2Rixwp5NiQTW0PCqRMn39Ty4tDeluNtLPYC4++e07pyaN05dXjm+qWZ71oJ -d3sAmzNKoXUU59k/hdaBpcX54Q9rGzMs0Ax6jusoNVgTbRilBjteMxylBtscBQCNe7iVOrA0Nb08 -NXfT4mwrg3Wrbmu5RYenmW1HzV1T6Z7pwsr8zWF/7hneo7qVOW/SaXRP8fDy7PL0wdtn54af48Li -bfilFs60NpsGRL0Ge9p0GzUgG8+/38ppbSz1/nQr3VDWY0k4GcOzT7BsS2s4dq28SaMEROtVYI3s -Ab1zBHvA8lQ7Uyuc+I5+x1yrfLTFmeV0Qm07L5sqzGTPCWmmjV48I2FmHcIMXMwWzjARZ0aM8knL -KLf08I1Y5fWd66EdnFrpj7AR96ZWTmjk3LQGYabiuPcNyKDUGj3Y2hycslae0c1xcGrn3EYOTseM -oxpZ1pqF0TZG6G+6LNrGSY5k0ZEsOpJFR7LoSBYdyaIjWXQki65XFm2jMWEki554czs5ZNGTOP9J -G4WY53L+k6Hd5lo6sY05zp0cod6jtC7HfaM2fr8WZdfaNrWN3bC2oo0NZatpIxEbpThF5Hf93OLi -QOHjhMF9QxTgPUHRXVtn1oTtDi8fGb4M+wE4gFRt+Jo756am797TIdDioanp2eUj1w== - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 8RqiRrlEpU7tzuITnSprF9wD/jbI2FeoAMxGyLt04Q2wMZXEsFRIBkxypA5M0BGYrugS1XEXmtTC -XawhOgB6m/kaSgxUK+ytLZKW3AOwuFrgxPoBsLLq+cJEYGTjkV2fkB5wvVGW8SZg8MkI1z5nOHDf -DMzEiod2Whpw6YgDRrcAOWzh0irnWEcVWG7kx8KisYXIgc2M2GVjSUOBLHsBOgc2zAW8qpDTC7Ie -CzpgSSWWMDA/qK4jWwIrfcK+GbharA0IskBJvKaOu6HBjE7mrzDywolkHBjuaCizMgRwDikQiwO9 -ZkUbmIUsKkEDU2kz8teIeAP4ROxhQtiuvDSm3hh2Q2dd3YazQ+b5+hgAffoclWrVgFHj4moTA/Yl -cG1l9yogJSrJQhqXDKhekH2LdG3BKBcOU969D0BOHQnB1aYFbhxMcfXtRTrvy6L7LCAcCVFycFKm -IB4xBOrSJ+dxQnogVBvP72SEx3PNhx1detg4Fa9FAw/SZZjLeaMAizk4NtEwF1jBnPwN5J4rsO0h -PQFnA8Zi4W3uPFsT0CUgCuGZyzzBrWJ9QQHcvWaWLnyLNT8+dEeeH3Co6YrpnG0HIHXlLGSCK5F3 -rLdxmSCVHLw1MkYJhSUMHS6CLUgzraOUWoANtmB214uWEi5TkJp5WxQTiYBGfVlyr+yCA/g9CJ45 -S7TAVE4I3lesOWL4ZB/4XAOcrNQARAyMThponY1EReBB0LG0mAXsaM4uOwZMBgDUbI0EcmmjUih3 -jOSBu83ZcQN8VUrNbila5WWn8USkhrnATuQkCoFIHe1y3lpGAqYkfjbw4wYHB2JlYopBuoVooQD8 -Ga1idIYKsOflpZjVkHxitwF9ik2LiRWYDoCzrRAOo1jQyhKnAj2QAj6M13rS2gZ0QJpKuNdhcFFS -RNzvCZGUdITQjEcYEmR9xiJBUhSvqIKEtui/JB42oG0jeQVOmTesADWgBSSgNdISccu0iLuihfdk -/RsjqyUxFODVpAkThlk69JJAFx8wak2IzSBw5cyxiRsX0GdtcrpJwrGBCa4QDWiikoBVMWIQ8iWb -OMLGBu6Nb4LYDCogeAgmHQgYfc3kaWFnFLEIufQaDkGhS2IyylJ2AuRvR/6GGu84iX2o90S+Q6xH -4VzY0rKKrKxsAxb0MlEblmsxFGknuF9rMjmEY+gZG4SjGQ39AFelMEqiGwJSVjgWn1GlR2MImJh1 -ishIRsucY7WiWPDCfIVp0Wxt9CC9syqkYrvRyEQbEC59IRY58GBUsjGFE6uazTUbUgOCFjudC5eP -T208XU6Jl1UAi9kGgI7kTidiPnyfSB8qZou8mhP70JRFFAcsmMTJtulTOaeguxsuU2IUBAMcmSY9 -0BJSr8H1NnnkbqwAS3agKVDzMy32KKVKtuAVYhNT4EvAemxBQOGGBOZabMFWRcOxkY7BkRH80+iG -BNTDjF9ZsLmzAoazY/NoVavgDtEgwXJ2ifEkm/CjfMGakfoQyHaFmhFWMgbcSDwp4ITK4ErUDpWf -WZ4cEPZpDHCr5YY4FgLhgDm5IYEQswRolUpUK1nhWIZDysJmOdBVZ2xAQX0+SbgKzxjII4WIvQEB -lSKMlJU/FcIFU4hIHvjXcDX4kqBzUbTL+VIodBhKyUxelpNKC7w02VvYi2ty2E2bW1EQFoGv9aTj -zsm6QD2gAg8VcYaEzl7ildrlPDm4pWQN/ds8Xs6qFzDLObbkx+eBKKgIMdQGBwyI6Z4J2OWsI1Yl -zhnscqb0cYEEEwC748g7TVZzUtg2VKbzlrBVLKxRFjePhSiw8WjRAWgb/f/Q9oNaEjkBxLO5qDAw -gRuvLHPhC6WcLSNGqaIgqiKHEIR46wRWHVjQ4ps883K4adKgkS6IsMVrgEYL8rKr3RmAhytYv2Bo -WCtLld7EFJbe2hQerzg9jbByRAYwWFbuAuYAKTnapcIh1jU0A0tTFIWvISRYR+42RV5ohGOPk4jo -AOjZKTCiROC02afMo3wTTWMm3C7va8gWeghyRF5Dy2g3zU0PDscdKsVmoaNhrvIBYcKAHWROdRER -AJd5rmoEB46CWHkiaQJgrmQESMeiYOBslhC9aJwL4qSILzoeyMC2lUJF8e9cIhQqagvWtpLUmUCa -fS526tyJOpOJOLR01R5Urn2oDC5tjT1AV8TM1RkJHIP14lEbuQ44CdYVvsaigGFO8IXwMnDu2T0N -HElhXJVNrNReeCQ6HshiFka4KeO7gAnnlYIjl4YWOKVlb4jz8sh2qh7mD83fRa5qnCKMl1RMCU8J -l8RbFBBrDCjY5ZzNdZ1bRTuQU3W+NuxUmZW9TDDYJnwMA2COudRRcKx4azBMGNXAiHvYQV3j2j1K -wk7V+Xv4XCH7VBcGQMFmdF6XHErDLnKpjAFmfkXWuppAUoonZCK9oNGSfKUqOQeW13tUsNeEIrjr -5AebSFBgmEOFaSpr9RKwyjBnKAbBgwO9Cg+oDHNeydUQ1yJQvdmCr3YGfuB1zTCoM/PE87rIhPGU -xtGzMO3WcwxCNQYWdgIGK1EAAaUZ2I1IMmJ3bFx4Y2pjwIUvreh4Svyr6Goc0DC5Iifd9q5DJdSi -erisBGa2zIEpX+macI32hlIZEcNpfglQJR7VAM8diiw1hQDGOFkjt7RkGwk4YIj3fzhF4owULiku -Z6KrAMtchezETzKwN1bL3WcNCDBjlmw3qC3JRbcVODrtxbc0PJYYyIJtvokSBh0MyVrGChvmH0t2 -mgWw8Y7Y3bC03plEE/Q2YfoDWyIxKmy6AW1fToaBqGBCTtP6qHuwYrqBoBFn64qKgNnBvc7WVBq9 -u1mZ5sLPzn27r11aroce7twHMYP7tm8rO7uu6NzxrWkAIYf9NUUC7l1ZXl5c6KjOjYsrh2c61y3e -u9AdBUjBfxjZN3QQHwi/Cr2VELV7ubAVGO6YIt2ph67JKQDwvRZTelmQNxkDpwUxsz9m1RjJkyJn -hdhtAuQhcAcCBjKADilprxAwhZrLdAgRyDdexps2jjNLuu1dhdDB3jtxNfG/vQfBzLrrrQsQvLq/ -c9fS1P7ZmYXljs6u6IwBrw10B37bTuHAZQOCL8twqTmekv7de1foJRytDrhMgX5dfo8hlDEPqawh -+gF8x/fOs/50797Rfm3KfmEoHkbE6oAALThThW1TgTUM6Ld7u8CdA0hB4O0MqEHl95h8QNPx1rEX -aK6DhFHbNLTQZ+CyR13DpcQ44Otn52auSWOCb506jNHDAS+EZm/cvu0dcTzIByJG67yzE5if8BOu -8MG+TQBEzejHdyAKWDlsHSCdLDx4Jz71uqnlqWs6JrBA27fdccO2Z9fyumzrdw/5s6ZuN/fRR2kw -GxnAxkeyKU/fyDA2dwDrGMbRGMCahnH0BjDkMI72AAYO49gMYJVhHMsB9BvGcR/DsR9A7zCO1xji -MI7jANozhuM7gNHP6Gf0M/oZ/Yx+Tqyf4046W8I/jPjJ485at1C+OPbDaBzAsRzGKgM4NsMYOICj -PYwhB3D0hrGmARyNYaxjAJs4ko08feMj2aynr28wa+rzlPpry5ZT42vLllP6v7acuvWMc3a8iF87 -zjlj66n9mp96+rkvedVr3nLzLfi6+S2vedVLzj19a1PrLVvPvvjVt86/94Ff5NcD752/7bU7d5x+ -akOnLxp7xw/9/G8/+HcP8+vvHvztX3jv7MQlZ2/tarr17EsmFj766b//2hNPPc2vp5742j88+In3 -vn1XV+OtZ+96+3t//a8ee/LpZ6oleiY0f/gzP/EdV9Qabz37iu/4ic88/I2nu1f0mace/dxP1hqf -evau7/jJzz361DPdTcPr6cdC411nywS3nH7J23/ic4/1dBob/8TbLzmdl27rjon3fubRPk1D40c/ -897rXkSj2HLG5bOfePip/sfgqYd/bWGMRrF1xxve//knmsYqE/zGX330HRfDKLDbr67SLQz50z/0 -mnNPHaLb8Hry73/+tpdsDYuwc1C3oeOv/fb8q87YsuWc17z3wQHdPvvMEw++9zXnbDl1x1se+LsB -3Yal+LsH3rLj1FNfdMvHHu67tnEQD3/slhdB218cpu0vnvxth1+zNezFWvZ4qLPz1Fc/MbszHOAh -zuQzT3z+/W/YsXWosw7dXn7GlqHukHQ71N2Uboe68xM7tq4dl6yGowihVTgq4r4nuls/04woA079 -xOe/GrDvM2nLr36+EQFfMjH7/l978B8qZA0tf+39TYj91NN3XH7d7Ht/oSICf/dgaHnd5U0EY8vW -M3bsfO1tFXF54L3Q8oxGQhRap0Qr0Kyd/Vpi65QY7jinmbglzbf0p7E7d75hYT/YlLdvu+NNVSZs -sW3fxAbxaw9Fh5U05XSTe8F8K9wLWuBkMZSHQ0EeKZBCIhsHZy43DsFeEGoP6YVpscMvZSGLpoHU -PMpqcD8zuUKfhzFKDRHGpstxi15n8iZ+QoFGOeVEKij/Vs3r4a2Jg1NfT6hdY/m4KpQp8yTP/969 -105Pr8zfurhcFZaNTlPg3oS+D5V7U/i9f/s2G72p9vU5Q/UP1neKoI8NniPsYoMniaayobO0c9/C -JuxKLa1+Z/dNi8u3zkwvLu0PJxI/H3Tcdt86MzV341R4xn3QvLNr4tob3shH+fYDi0vz9Jmcgc6u -a/cv3jmz79obyn1h4LctH5mb2VcNQQYVqwGEuVx7Q+faleXFDuGf2XcnRX16+7z5zsMzS/fM7N/3 -lpkj+6jV4fr5C1fu2gXww7Mup/O2Ij/ifsfuOPBfb0r9MQy6c9F/0+pMsQtXAodEVT46s5YFJ0ST -9HuQkrOkhFUMjPkXMBy41hgchjNMR5B0mwB5DNPi4SrwPM1RRN3CTedjGYcgsJj+hoabNq3mVfXZ -uwjkONkXVey+cerw3fQ26+zeu7g419l1w8I9M0vLM/t5iyJ8Ym720KGwd93w62YPQ4FDaa8EPjm7 -cHeE3vEa+HeFvghUaue+mrfWvl5/rXDMxGNr577UIcuVGBnpOu/sqIA8M/bZ6tsIQdiQ/vWJP5o4 -be2ruW2pvECvrVXJ9Smn/M5Xf1ve/v6TgXt58g8IzMwMfvgV/uMf009+B9v9IXznqd9f9REwsEj5 -wzK+Ka5VQv137rup3wbH3QgXsXP9yrvffaQDW16/fjv34QGpXGf33XEVIY7QY4Ilwl3u/TKto2YX -vNX8YnuISsuOZYpRTobjefWeMXl71Q3vWp59yzgdqbeu/ND7f/i+t10Of6h9//YDP/aB75su4I/L -blv63h/83sNvvZy+c+3kzZN7r3xOH8/uyi09XJFiZNaPAnV/sh4ahH1skApRHxuhQzSTDVGiVbij -Y8BdDMA5G4sfuClwVb0VhISFgTPS46282llclydzAUmwOGgC372zU7hO4Rt9mhsao3czfoH+Hejd -bAPHvXbvZnq93vzAwJ91dLvpYzgGo1rfMDZ9SBsfxmYNaXNHsu4hHaVhrGM8R3skww== - ]]> - <![CDATA[ - j+fYjGSY8RzLkQwcT3sGc+xHssp4jtdgesdzHEfStsGk4znuI2nnYI77MNLxHPcxjAZzYg1m9DP6 -Gf2clD+twjOjwawymJaMp4XMVUvG057BtFluOo6D6R3J8RpPv5Ecl/G0ZzCrj+RYjmeYkRyb8Qw/ -kqM6pHUM4yiNZyMj2cQhbcowNj6kTR/GOgZ2DMZwtF89dujKdXLV6ARqe+rW004/cxu+zjz9tP4B -CtT4tDO277jw4st2htdlF1+4Y/sZp/X9Qmh81o6LrvBvuv3b3vHt3/6Ob7v9Tf6Ki3ac9fx+vqRn -7rjEvPk7Dt//oZ/8j+H1kx+6//B3vNlc+uKzGp6w5dTnb79Q3TR//4d/5Q8+8+BD4fXgZ/7gVz58 -/8K3uEt6XVDRqfV10/f/3O997n88/Mjj/xxejz/y8P/43O//wr9fvCm7YFu9/Zat2y5Qt6z86G9+ -/iuPPvEv7F/71L888eg/fuEPf+7+7ywvPCttH1pf6L/z/o/9yd8++s3EdfeZp5/65uP/+PlPvu/A -NRem/Z96xgV+5n2ffOirX+/1Cv7mI1/+vQ8eKC84I7rlbjlth/rO9/3ulx/95tO9HsfPPPn43/ze -+75T7Xi+dL/1rEtvvv+TX37syUaX6meeevzLn7z/lp3beThbnv9it/hzn3+kuTX0/+hDH1t+3ct4 -OKHzW//9H/7jN/u6az/zzYf/5Eemdp/3vC3UuT/0C198fBWH7ae//je/8b1vfDl2f+pZl37Lqp3D -cL722Z945xXbt+KymPmf/8JqnUP3X/6Vw37HaVtO2XL6hW/8gd/9ymqdh+7/5eE/uP+Gl5++5ZRT -z7z49g9++pFBzvGPfPZHvvXSbaeecuq2y97+4QcfH+DQ//Q/f/GB/VefszU03/ntH3nonwc0f+Yb -f/OrK+WLTtsCzf/j4OYw+DddePqQzcPgP/PB2y8+89Rhmz/2uR+749Lhm6+p99rY17Qya1v3te3q -Gs/MGk/kGs+7XNXm2CjpvLpNhAh+62+/3ndxancVRq+nf/RPvtpvOF2YIKzlha9b+dhDfTDHM08+ -VsMzAUVu33nL/b/xpUcasNgzT3/z0S//bg2LhdnuUPv+n99oxJFf/+pDn3zfjE9wJOJUty9i4Gdi -z09989G//ZOP3f+dvoaBU/z+yD9/gxE84PevfP43f3TlFtVLD5h6/O5nv/g3RD6Qevzez90//bqG -gAehTT/4Iw98nIgT0ab5m9SF25/fQC2R8hVvfNv+FSJ9TPku2XFmH0oZ6OoLL7z0aiKska6uSohP -33bOi4hsD6La+IWEKRjIE8SvDMtxDHwNju5YJbQD3N4pMyuWuYXM/vNdcKiWwiU8fDZO1XSg/kEe -S6Fg2SIBxaiKwpSm1hSc3rnuStVnApQBxNyVUk7axNzHsVvwPMqdL5MRRFAMLcGxpi3jpJI+G1aA -4zqaYjmM5lgOD+s4Vob1HFeZSd0uKUslph7NsLBdATnCFf0uvOtUn2DCXEmMayiX/h3zPV/GctGr -f7uA4ihhtfZO176dF1QgbMC3oSId+PbtXc+zISMzZv/eO10PNoHAEG9Wc4Ec1j8fcj5TEdmuQxrh -UL2aS9N7JcVhsTAr1QiDM0IlPwQY65w5rKqUNIbE5k6pstZtAuw6pwLH1OhlqTtptxCPIvU94xgi -MJY+ogGnjePUkm4b1oGWXI0WbA0LNkzMlnJ0zxV0no3nBgolQiVFyB8Pdad93nPls3GT5VAGF2Kz -ylxVv6tPyGU2s3zrPBR9705Nm8WeJTwlG9dZxlVwrcmkdDAkMsfiR5CuW+eZeNSGaym1rou4mTpn -N10NIUgxx73PpC42Ju5mHOuMpV6leDwCvZEhxELtCMdVhwIQmhGy5YrEuD98yqoxQFk4HyMAoYCo -ErAphM5QgTWqiUwliXpXYWIVPG0xq3AJIXeIpgM1cLo3mXD4EGYOVQ3HtKFiwIzlKFzMx5Ho8Hit -ED9D+BTgNkiQvvq3tKak64Abw7egdsXgL4VJY7WLvWt6FGRnV4Wto+E2hUiQs/umhUp4rHOjbOed -ncBeKMm+3LcRgKgh/Ts4VCK3fphQiQGvN935rqnre6DX33XPDz7wa7/18f/0777nyNLMGyJ8YuHH -f/2/P/QPjzz2yMN//Zef/qP/9h/u2sNf+IGPf+FrT1YiyVf//D/d5+GD4r5f+usna4z/1z71wZux -rx/47YfrIsFjf/bBm+CT/L5f/qsn0g+e/Nv/9q+uoef8n//5U19+hIWbZ/7lf3/h4993A4/tdXf+ -q5/+zU995i/C688//ce/9mPz1yUTevNdh+898p73vOfIvYf2v+GUdb9OhKCQm7tx9OqRIQqZHsBU -ekOYCu5zPb4EGd9V+KvmsG414hI2i0somUuAyhPAJQQeeQxlm4B2oMqS6S02MB5QOTSD0kRYBqd6 -Ix9RLW1rmNYp3RXIfTIjdaUEXxcBLatmpF41wrATbEj/DhH/ZouhAuA2/lp434c/8v/Ord5m/gMP -/Ne/fuypp7/2pV994AOL/Vp91099/rEKVz/+0EcWGvv6yBe+UUf3j3/xpw/1NHvPnz7eq4b6+p8d -6Wr23X/RpK569tnP/ut6b32aPfvsX6Y9Lvxpv2bPPvun81W7n/p6/3aP/lRs9n98uX+zZ5/9m+/h -Znf/99WaPfvsp95F7T74yOrt/vcHqN1/Wb3Zs8/+F5rsXw1q96Xvgnbvf2xQuyfeB+0+OiBNTaDT -H4V2Dwxq9uyzPwPtfnZwu589Gu2GGN8Da5nvB1Y5LPR6HDdu4QuD2n2BjuAvD2pH+3vKjwx48Nc+ -RO0OPbR6u8/Jjf/w11Zr9k8/Luf+7j9bpdk3f79CIP/6s32bPfnH70ku5pG/7NfsU/+qdtG/57ON -u/LNP643O+WUez/9jd5m//T77zml+7X0Uw91reOjf/njjTjw0E99MeFWH/nzjy41tcI+P/RLX3gk -cMNPfu3zv/T+XtyXvubf95EHHvjo+1ZvtAmvk5qjTTmkJu52HRxSL6Ob17iLEZd7jLlc61BCgS3M -oF6o6aDaW0M93H5qb8guM26gOqf8TpUWYTKmxMqBcBRKp0b87QnI356yZcsQDjNbTt269XnP27p1 -VVPXlq3PO+20088Ir9NPO+15/VKdhVbPP+PMs87efs45288+68wznt/YcsupodVZ288974U7Xvzi -HS8879ztZ4WWPUY5cM4565zzdpz/0gteFl4XvPT8Heedc1aPqQ+anf2CHS+54MKLXvHKTueVr7jo -wgtesuMFZ3c3BFef886/4OWv6Fxy6WU7L7vs0ks6r3j5BeefB8bDtLvnnXH2eee/7KLOpTsv33Xl -lVfu2nX5zks7F73s/PPOPuN5VYdbtp5+1gvOf9krLr7sVVdePTYeXmNXXfmqyy5+xcvOf0HqEnTq -aWee++KXveKSnbuuGt+dKa1Vtnvsql07L77oghefe2Z8cnjq9he+9KKLd+66ercK99aYItfZ+NW7 -Lrv45S99YfXkLadtO/f8CzuXQbPCWOe9syZX41e96tJXvgw6lCyhp58durvk8qugkKYvr7nmmnCJ -bWh45eUXX/TS88II5bHnvPhlr7xs11iWh2Z79rz61XuuKZ3Js6uhwx3b+cFbnnfmuee//OKdV41r -48o9r35teL16D5QLH79yZ+fC8889Uyzv217wkvDYq3eH7q559WtfF16vffU13ujdV11+8ctf8oJt -p0m788LwXnV1eGzo7nWvf/3rX/e60M7mu6++/JKLXnJebHfWeS99xaXc7rXQ7vWve/We0C4M8JIw -kT79hYb43N7+aHxX7dYmjm/PNa5nfDjfzs4roWZ4ec2rXw3TDd31zFfWLzw4LMw1e8IL1k/vpvU7 -RzZO9iN0GBpCPfXSwzKPXbkT9uPs07fG/X3B+Re+8tJXhY3LCws1620RDkL3/tJ5eXnnstAQ6jCH -V66ysatedVnn5S9Jzks4f9vOffEFF4VjetVYoBAqHL/xq8NB7Vx0wY5zqvMXOjz97HCeQ8PLd111 -9Vh4XX3lrsvDwb/gxS84K01tCLctXKOLOpdcdvmrdoXXqy6/7JJXvjw0C09NLhJdyxe/9MJXdC6+ -9DK4bhe/8qILX7oDm6UXExuey/c3vF7x8gtfev4Lz22+6NvOeUHEBy958QtfsH3b6U2IA/HLC87b -Ifhl2xmn9TY7hfHVtrMCuiJ81dyKW572/DMQ/z0fWvVHlVu2ADoNCHWw68CWLcMg6A2/ntP87Vp4 -o2YNrl81deIg3hb4zpLq0QtbW4EUVJBHrjYft1jXFxhVLGfezdUicFpKZ+e6cLXGUC1cOywwH3tN -YTGZZgXy46UizjX2V2qqbl57uACZnY0jTRonU4q91ma+isMGatcVVB7POqhc97ldlW1V494FjjwH -FrVUKdvqwcpKs3NlYFsLVXPWCN8141lAqQO+awPLqyxbBKGGNBQ5Hu5xe9fxOM9lqp87ZkHKqhmY -51x3dHMGtaQRgKgh/TuYwz4qDLa96W1vmzSNH5mb3jG98H//6Ec/8qF/d2j/t99suz59+7/5Dz/7 -y5/81Oceeuhzf/Jb//XnfvR73+mTj/ce+fAnP/ulf/hfj3/zySe/+fj/+sqXP/c7P/O936r50123 -fP8vffYfUs/Hp594+MFf/8BByvZ2yuT3/+pDj3brfb7+pd/64W/Bj8cOPfDZBtXp4w/+wmHs4HX/ -9uNffrL386f+569/37Xw+esHfH714s809f+Y9H/KW77/4194rGt8Tz/217/5wzfT+K+85ft++c+/ -kjqaPvnY3//FJ/6//Vlcn/f85G999kt//0+PPfEvT37jsYf/5xf/7JMf/TdvVcn6vuN7PvTAL/36 -H/1FWN8/+sR//ukPfPe3p+sbXsWNb59e+L9+7KMf/dD3L9z59hubN9GH/X2La/xova+TmvA2Etuh -cHkvmS27Se5qFLeWlq9Rd9T1ybq0R5QueGP6I+pjYxokSsy3MR3Sapn5BgPGSpOX2qjMd3bfEDZ5 -1+1LUwuHIdHwNeHN/sX5zuGZSDjCSctVU6LjdXVl+/VEGQI78etJ0uX4+FXa2MFNVDZEGzO4jR5i -ONom9xRTH96wcHh5amF6Zh/gjn03XBeu7qFNW9U4tU3oymxaV3rzjk3vegL2e8N9M9Mr0HF9dY9t -AuziKOap1H3yVGbd8omOqLU/qgclpxsP/Dyy+pxvuwIBzrHkTejxbYnIDD525E2okr+5FIAdD+TA -1tsFZtsopWs9VkB4Mn25AkE3TpFvnfRY0MdOnhz/ZqlHRpi0S6ZX9Vif82pu5ld0KqfoOnkzOqBx -zgU6Zmz4w9ha10aVNH0QTrweL2xA092ty3LcGqeT1iAf5OPGKdPbOvStyqxMW4e+tQ70KSxPT+uc -4PXWQSAxmerpm3cyaagCjVdlb0PInm+GbOuKIEaS61/Sus9iJIckXYwBwtKu0DSItQ== - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> - <![CDATA[ - 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 - ]]> -</i:pgf> -</svg> diff --git a/branding/icons/primary/numpylogo.png b/branding/icons/primary/numpylogo.png Binary files differnew file mode 100644 index 000000000..8187b49c1 --- /dev/null +++ b/branding/icons/primary/numpylogo.png diff --git a/branding/icons/primary/numpylogo.svg b/branding/icons/primary/numpylogo.svg new file mode 100644 index 000000000..e5791dc3e --- /dev/null +++ b/branding/icons/primary/numpylogo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 721.86 324.74"><defs><style>.cls-1{fill:#4d77cf;}.cls-2{fill:#4dabcf;}</style></defs><g id="Layer_1" data-name="Layer 1"><path class="cls-1" d="M299.23,125a5.76,5.76,0,0,1,1.62.45,5.58,5.58,0,0,1,1.38.93,17,17,0,0,1,1.49,1.62l41.51,52.47c-.17-1.67-.28-3.31-.36-4.88s-.12-3.07-.12-4.47V124.83h17.87v87.38H352.06a9.68,9.68,0,0,1-3.95-.72,8.47,8.47,0,0,1-3.12-2.64l-41.21-52c.13,1.51.22,3,.3,4.46s.13,2.83.13,4.11v46.84H286.33V124.83H297A17.21,17.21,0,0,1,299.23,125Z"/><path class="cls-1" d="M392,150v39.46q0,4.62,2.1,7.14a7.62,7.62,0,0,0,6.18,2.52,13.26,13.26,0,0,0,5.73-1.26,21.37,21.37,0,0,0,5.19-3.54V150h18.59v62.19H418.28a4.39,4.39,0,0,1-4.57-3.12l-1.13-3.6a37.32,37.32,0,0,1-3.72,3.16,23.32,23.32,0,0,1-4.11,2.39A24.55,24.55,0,0,1,400,212.6a25,25,0,0,1-5.51.57,21.75,21.75,0,0,1-9-1.77,18.67,18.67,0,0,1-6.63-4.94,21.68,21.68,0,0,1-4.08-7.5,31,31,0,0,1-1.38-9.48V150Z"/><path class="cls-1" d="M441.78,212.21V150H453.3a5.13,5.13,0,0,1,2.91.78,4.21,4.21,0,0,1,1.65,2.34l1,3.36a33.22,33.22,0,0,1,3.23-3,20.83,20.83,0,0,1,3.63-2.34,19.68,19.68,0,0,1,9.15-2.13,14.6,14.6,0,0,1,9.33,2.91,18.14,18.14,0,0,1,5.6,7.76,18.71,18.71,0,0,1,3.81-4.92,20.42,20.42,0,0,1,4.86-3.29,23.69,23.69,0,0,1,5.51-1.86,28.63,28.63,0,0,1,5.8-.6,26.3,26.3,0,0,1,9.47,1.59,18.05,18.05,0,0,1,6.93,4.62,20.15,20.15,0,0,1,4.23,7.44,32,32,0,0,1,1.44,10v39.52h-18.6V172.69q0-9.66-8.27-9.65a8.46,8.46,0,0,0-6.27,2.49q-2.49,2.47-2.49,7.16v39.52H477.65V172.69q0-5.34-2.1-7.5c-1.4-1.44-3.46-2.15-6.18-2.15a10.53,10.53,0,0,0-4.77,1.13,17.72,17.72,0,0,0-4.23,3.06v45Z"/><path class="cls-1" d="M562.93,183v29.21H542.66V124.83h30.82A50.86,50.86,0,0,1,589.35,127a30.49,30.49,0,0,1,10.91,6,23.36,23.36,0,0,1,6.33,9.06,30.63,30.63,0,0,1,2,11.27,33.11,33.11,0,0,1-2.1,12,24.08,24.08,0,0,1-6.42,9.36,30,30,0,0,1-10.94,6.08A49.9,49.9,0,0,1,573.48,183Zm0-15.29h10.55c5.28,0,9.08-1.25,11.4-3.78s3.48-6,3.48-10.55a15.79,15.79,0,0,0-.9-5.46,11.11,11.11,0,0,0-2.73-4.23,12.41,12.41,0,0,0-4.62-2.73,20.71,20.71,0,0,0-6.63-1H562.93Z"/><path class="cls-1" d="M644.61,228.35a6.69,6.69,0,0,1-2,2.72,6.62,6.62,0,0,1-3.84.87H624.82l12-25.18L612,150h16.43a5.25,5.25,0,0,1,3.36,1,5.15,5.15,0,0,1,1.68,2.28l10.19,26.81A59,59,0,0,1,646,187.5c.4-1.28.84-2.54,1.32-3.77s.94-2.5,1.38-3.78l9.24-26.69a4.5,4.5,0,0,1,1.89-2.31,5.4,5.4,0,0,1,3-.93h15Z"/><polygon class="cls-2" points="132.38 96.4 95.25 77.66 54.49 98 92.63 117.15 132.38 96.4"/><polygon class="cls-2" points="149.41 104.99 188.34 124.65 147.95 144.93 109.75 125.75 149.41 104.99"/><polygon class="cls-2" points="201.41 77.94 241.41 98 205.63 115.96 166.62 96.28 201.41 77.94"/><polygon class="cls-2" points="184.19 69.3 148.18 51.24 112.56 69.02 149.67 87.73 184.19 69.3"/><polygon class="cls-2" points="156.04 224.36 156.04 273.5 199.66 251.73 199.62 202.57 156.04 224.36"/><polygon class="cls-2" points="199.6 185.41 199.55 136.77 156.04 158.4 156.04 207.06 199.6 185.41"/><polygon class="cls-2" points="251.97 176.3 251.97 225.63 214.76 244.19 214.73 195.09 251.97 176.3"/><polygon class="cls-2" points="251.97 159.05 251.97 110.71 214.69 129.24 214.72 177.98 251.97 159.05"/><path class="cls-1" d="M140.64,158.4l-29.38-14.78v63.84S75.32,131,72,124.13c-.43-.89-2.19-1.86-2.64-2.1C62.88,118.65,44,109.09,44,109.09V221.92l26.12,14v-59s35.55,68.32,35.92,69.07,3.92,7.94,7.74,10.47c5.07,3.37,26.84,16.46,26.84,16.46Z"/></g></svg>
\ No newline at end of file diff --git a/branding/icons/primary/numpylogodark.png b/branding/icons/primary/numpylogodark.png Binary files differnew file mode 100644 index 000000000..e6d20af6b --- /dev/null +++ b/branding/icons/primary/numpylogodark.png diff --git a/branding/icons/primary/numpylogolight.png b/branding/icons/primary/numpylogolight.png Binary files differnew file mode 100644 index 000000000..8500d1c10 --- /dev/null +++ b/branding/icons/primary/numpylogolight.png diff --git a/branding/icons/secondary/numpylogo2.png b/branding/icons/secondary/numpylogo2.png Binary files differnew file mode 100644 index 000000000..000a197b3 --- /dev/null +++ b/branding/icons/secondary/numpylogo2.png diff --git a/branding/icons/secondary/numpylogo2.svg b/branding/icons/secondary/numpylogo2.svg new file mode 100644 index 000000000..76b076beb --- /dev/null +++ b/branding/icons/secondary/numpylogo2.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><defs><style>.cls-1{fill:#4d77cf;}.cls-2{fill:#4dabcf;}</style></defs><g id="Layer_1" data-name="Layer 1"><path class="cls-1" d="M69.36,351.63a5.46,5.46,0,0,1,1.59.44,5.58,5.58,0,0,1,1.37.92,17.85,17.85,0,0,1,1.48,1.6l41,51.84c-.16-1.66-.28-3.27-.36-4.82s-.12-3-.12-4.42V351.51H132v86.32H121.55a9.69,9.69,0,0,1-3.91-.71,8.35,8.35,0,0,1-3.08-2.61l-40.7-51.42q.18,2.25.3,4.41c.07,1.44.11,2.79.11,4.06v46.27H56.62V351.51H67.16A17,17,0,0,1,69.36,351.63Z"/><path class="cls-1" d="M161,376.39v39q0,4.56,2.07,7.05a7.54,7.54,0,0,0,6.11,2.49,13,13,0,0,0,5.65-1.25,21.12,21.12,0,0,0,5.13-3.49V376.39h18.37v61.44H187a4.31,4.31,0,0,1-4.5-3.08l-1.13-3.56a35.28,35.28,0,0,1-3.67,3.12,22.21,22.21,0,0,1-4.06,2.37,24.63,24.63,0,0,1-4.65,1.54,25.07,25.07,0,0,1-5.45.56,21.61,21.61,0,0,1-8.92-1.75,18.31,18.31,0,0,1-6.55-4.88,21.4,21.4,0,0,1-4-7.41,30.59,30.59,0,0,1-1.37-9.36v-39Z"/><path class="cls-1" d="M210.19,437.83V376.39h11.37a5.1,5.1,0,0,1,2.87.77,4.09,4.09,0,0,1,1.63,2.32l1,3.31a33.63,33.63,0,0,1,3.2-2.93A20,20,0,0,1,238,376a19.66,19.66,0,0,1,4.89-.56,14.39,14.39,0,0,1,9.21,2.87,17.82,17.82,0,0,1,5.54,7.67,18.28,18.28,0,0,1,3.77-4.86,20.07,20.07,0,0,1,4.79-3.25,22.73,22.73,0,0,1,5.45-1.84,28,28,0,0,1,5.72-.59,25.87,25.87,0,0,1,9.36,1.57,17.7,17.7,0,0,1,6.85,4.56,20,20,0,0,1,4.18,7.35,31.93,31.93,0,0,1,1.42,9.86v39H280.81v-39q0-9.54-8.18-9.54a8.4,8.4,0,0,0-6.19,2.46c-1.64,1.64-2.46,4-2.46,7.08v39H245.62v-39c0-3.52-.7-6-2.08-7.41s-3.42-2.13-6.1-2.13a10.4,10.4,0,0,0-4.71,1.12,17.42,17.42,0,0,0-4.18,3v44.43Z"/><path class="cls-1" d="M329.86,409v28.85h-20V351.51h30.45A50.26,50.26,0,0,1,356,353.67a30.21,30.21,0,0,1,10.79,6,23.05,23.05,0,0,1,6.24,8.95,30.12,30.12,0,0,1,2,11.13,32.77,32.77,0,0,1-2.07,11.85,23.87,23.87,0,0,1-6.34,9.24,29.85,29.85,0,0,1-10.82,6A49.24,49.24,0,0,1,340.29,409Zm0-15.11h10.43q7.81,0,11.26-3.73T355,379.71a15.67,15.67,0,0,0-.89-5.39,10.94,10.94,0,0,0-2.7-4.17,12,12,0,0,0-4.56-2.7,20.26,20.26,0,0,0-6.55-.95H329.86Z"/><path class="cls-1" d="M410.56,453.77a5.24,5.24,0,0,1-5.81,3.55H391l11.85-24.87-24.53-56.06h16.23a5.21,5.21,0,0,1,3.32.95,5.1,5.1,0,0,1,1.66,2.25l10.07,26.49a57.53,57.53,0,0,1,2.31,7.34c.4-1.26.83-2.51,1.3-3.73s.93-2.46,1.37-3.73l9.12-26.37a4.58,4.58,0,0,1,1.87-2.28,5.34,5.34,0,0,1,3-.92h14.81Z"/><polygon class="cls-2" points="229.82 96.34 181.83 72.11 129.15 98.4 178.44 123.16 229.82 96.34"/><polygon class="cls-2" points="251.82 107.45 302.15 132.85 249.94 159.07 200.56 134.27 251.82 107.45"/><polygon class="cls-2" points="319.04 72.48 370.73 98.4 324.5 121.63 274.07 96.19 319.04 72.48"/><polygon class="cls-2" points="296.78 61.32 250.24 37.98 204.2 60.95 252.16 85.14 296.78 61.32"/><polygon class="cls-2" points="260.39 261.73 260.39 325.25 316.78 297.11 316.72 233.57 260.39 261.73"/><polygon class="cls-2" points="316.7 211.39 316.63 148.52 260.39 176.47 260.39 239.37 316.7 211.39"/><polygon class="cls-2" points="384.39 199.61 384.39 263.37 336.3 287.37 336.26 223.9 384.39 199.61"/><polygon class="cls-2" points="384.39 177.31 384.39 114.84 336.21 138.79 336.25 201.78 384.39 177.31"/><path class="cls-1" d="M240.49,176.47l-38-19.09v82.5s-46.44-98.82-50.75-107.7c-.55-1.15-2.83-2.4-3.42-2.71-8.36-4.37-32.73-16.73-32.73-16.73V258.57l33.76,18.05V200.4s45.95,88.31,46.42,89.27,5.08,10.27,10,13.54c6.57,4.35,34.7,21.27,34.7,21.27Z"/></g></svg>
\ No newline at end of file diff --git a/branding/icons/secondary/numpylogo2dark.png b/branding/icons/secondary/numpylogo2dark.png Binary files differnew file mode 100644 index 000000000..3c866703c --- /dev/null +++ b/branding/icons/secondary/numpylogo2dark.png diff --git a/branding/icons/secondary/numpylogo2light.png b/branding/icons/secondary/numpylogo2light.png Binary files differnew file mode 100644 index 000000000..98f00bc42 --- /dev/null +++ b/branding/icons/secondary/numpylogo2light.png diff --git a/doc/HOWTO_RELEASE.rst.txt b/doc/HOWTO_RELEASE.rst.txt index bd15f7f50..5a9c4d505 100644 --- a/doc/HOWTO_RELEASE.rst.txt +++ b/doc/HOWTO_RELEASE.rst.txt @@ -390,8 +390,11 @@ create an archive of the documentation in the numpy/doc repo:: # This checks out github.com/numpy/doc and adds (``git add``) the # documentation to the checked out repo. make merge-doc - # Now edit the ``index.html`` file in the repo to reflect the new content, - # and commit the changes + # Now edit the ``index.html`` file in the repo to reflect the new content. + # If the documentation is for a non-patch release (e.g. 1.19 -> 1.20), + # make sure to update the ``stable`` symlink to point to the new directory. + ln -sfn <latest_stable_directory> stable + # Commit the changes git -C build/merge commit -am "Add documentation for <version>" # Push to numpy/doc repo git -C build/merge push diff --git a/doc/TESTS.rst.txt b/doc/TESTS.rst.txt index 4ab39c586..d1af7017b 100644 --- a/doc/TESTS.rst.txt +++ b/doc/TESTS.rst.txt @@ -136,43 +136,24 @@ modules and develop tests for it. Labeling tests -------------- -As an alternative to ``pytest.mark.<label>``, there are a number of labels you -can use. - Unlabeled tests like the ones above are run in the default ``numpy.test()`` run. If you want to label your test as slow - and therefore reserved for a full ``numpy.test(label='full')`` run, you -can label it with a decorator:: +can label it with ``pytest.mark.slow``:: - # numpy.testing module includes 'import decorators as dec' - from numpy.testing import dec, assert_ + import pytest - @dec.slow + @pytest.mark.slow def test_big(self): print 'Big, slow test' Similarly for methods:: class test_zzz: - @dec.slow + @pytest.mark.slow def test_simple(self): assert_(zzz() == 'Hello from zzz') -Available labels are: - -- ``slow``: marks a test as taking a long time -- ``setastest(tf)``: work-around for test discovery when the test name is - non conformant -- ``skipif(condition, msg=None)``: skips the test when ``eval(condition)`` is - ``True`` -- ``knownfailureif(fail_cond, msg=None)``: will avoid running the test if - ``eval(fail_cond)`` is ``True``, useful for tests that conditionally segfault -- ``deprecated(conditional=True)``: filters deprecation warnings emitted in the - test -- ``paramaterize(var, input)``: an alternative to - `pytest.mark.paramaterized - <https://docs.pytest.org/en/latest/parametrize.html>`_ - Easier setup and teardown functions / methods --------------------------------------------- diff --git a/doc/changelog/1.19.0-changelog.rst b/doc/changelog/1.19.0-changelog.rst new file mode 100644 index 000000000..bd743832a --- /dev/null +++ b/doc/changelog/1.19.0-changelog.rst @@ -0,0 +1,592 @@ + +Contributors +============ + +A total of 126 people contributed to this release. People with a "+" by their +names contributed a patch for the first time. + +* Alex Henrie +* Alexandre de Siqueira + +* Andras Deak +* Andrea Sangalli + +* Andreas Klöckner + +* Andrei Shirobokov + +* Anirudh Subramanian + +* Anne Bonner +* Anton Ritter-Gogerly + +* Benjamin Trendelkamp-Schroer + +* Bharat Raghunathan +* Brandt Bucher + +* Brian Wignall +* Bui Duc Minh + +* Changqing Li + +* Charles Harris +* Chris Barker +* Chris Holland + +* Christian Kastner + +* Chunlin + +* Chunlin Fang + +* Damien Caliste + +* Dan Allan +* Daniel Hrisca +* Daniel Povey + +* Dustan Levenstein + +* Emmanuelle Gouillart + +* Eric Larson +* Eric M. Bray +* Eric Mariasis + +* Eric Wieser +* Erik Welch + +* Fabio Zeiser + +* Gabriel Gerlero + +* Ganesh Kathiresan + +* Gengxin Xie + +* Guilherme Leobas +* Guillaume Peillex + +* Hameer Abbasi +* Hao Jin + +* Harshal Prakash Patankar + +* Heshy Roskes + +* Himanshu Garg + +* Huon Wilson + +* John Han + +* John Kirkham +* Jon Dufresne +* Jon Morris + +* Josh Wilson +* Justus Magin +* Kai Striega +* Kerem Hallaç + +* Kevin Sheppard +* Kirill Zinovjev + +* Marcin Podhajski + +* Mark Harfouche +* Marten van Kerkwijk +* Martin Michlmayr + +* Masashi Kishimoto + +* Mathieu Lamarre +* Matt Hancock + +* MatteoRaso + +* Matthew Harrigan +* Matthias Bussonnier +* Matti Picus +* Max Balandat + +* Maximilian Konrad + +* Maxwell Aladago +* Maxwell Bileschi + +* Melissa Weber Mendonça + +* Michael Felt +* Michael Hirsch + +* Mike Taves +* Nico Schlömer +* Pan Jan + +* Paul Rougieux + +* Pauli Virtanen +* Peter Andreas Entschev +* Petre-Flaviu Gostin + +* Pierre de Buyl +* Piotr Gaiński + +* Przemyslaw Bartosik + +* Raghuveer Devulapalli +* Rakesh Vasudevan + +* Ralf Gommers +* RenaRuirui + +* Robert Kern +* Roman Yurchak +* Ross Barnowski + +* Ryan + +* Ryan Soklaski +* Sanjeev Kumar + +* SanthoshBala18 + +* Sayed Adel + +* Sebastian Berg +* Seth Troisi +* Sha Liu + +* Siba Smarak Panigrahi + +* Simon Gasse + +* Stephan Hoyer +* Steve Dower + +* Thomas A Caswell +* Till Hoffmann + +* Tim Hoffmann +* Tina Oberoi + +* Tirth Patel +* Tyler Reddy +* Warren Weckesser +* Wojciech Rzadkowski + +* Xavier Thomas + +* Yilin LI + +* Zac Hatfield-Dodds + +* Zé Vinícius + +* @Adam + +* @Anthony + +* @Jim + +* @bartosz-grabowski + +* @dojafrat + +* @gamboon + +* @jfbu + +* @keremh + +* @mayeut + +* @ndunnewind + +* @nglinh + +* @shreepads + +* @sslivkoff + + + +Pull requests merged +==================== + +A total of 452 pull requests were merged for this release. + +* `#8255 <https://github.com/numpy/numpy/pull/8255>`__: ENH: add identity kwarg to frompyfunc +* `#12646 <https://github.com/numpy/numpy/pull/12646>`__: TST: check exception details in refguide_check.py +* `#13421 <https://github.com/numpy/numpy/pull/13421>`__: ENH: improve runtime detection of CPU features +* `#14326 <https://github.com/numpy/numpy/pull/14326>`__: TST: Add assert_array_equal test for big integer arrays. +* `#14376 <https://github.com/numpy/numpy/pull/14376>`__: MAINT: Remove unnecessary 'from __future__ import ...' statements +* `#14530 <https://github.com/numpy/numpy/pull/14530>`__: MAINT: Fix typos and copy edit NEP-0030. +* `#14546 <https://github.com/numpy/numpy/pull/14546>`__: DOC: NumPy for absolute beginners tutorial +* `#14715 <https://github.com/numpy/numpy/pull/14715>`__: NEP: Proposal for array creation dispatching with `__array_function__` +* `#14867 <https://github.com/numpy/numpy/pull/14867>`__: ENH: Use AVX-512F for np.maximum and np.minimum +* `#14924 <https://github.com/numpy/numpy/pull/14924>`__: BUG: Fix numpy.random.dirichlet returns NaN for small 'alpha'... +* `#14933 <https://github.com/numpy/numpy/pull/14933>`__: API: Use `ResultType` in `PyArray_ConvertToCommonType` +* `#14942 <https://github.com/numpy/numpy/pull/14942>`__: MAINT,API: ignore and NULL fasttake/fastputmask ArrFuncs slots +* `#14981 <https://github.com/numpy/numpy/pull/14981>`__: BUG: Make ``ediff1d`` kwarg casting consistent +* `#14988 <https://github.com/numpy/numpy/pull/14988>`__: DOC: linalg: Include information about scipy.linalg. +* `#14995 <https://github.com/numpy/numpy/pull/14995>`__: BUG: Use ``__array__`` during dimension discovery +* `#15011 <https://github.com/numpy/numpy/pull/15011>`__: MAINT: cleanup compat.py3k.py +* `#15022 <https://github.com/numpy/numpy/pull/15022>`__: ENH: f2py: improve error messages +* `#15028 <https://github.com/numpy/numpy/pull/15028>`__: [DOC] LaTeX: fix preamble (closes #15026) +* `#15035 <https://github.com/numpy/numpy/pull/15035>`__: BUG: add endfunction, endsubroutine to valid fortran end words +* `#15040 <https://github.com/numpy/numpy/pull/15040>`__: TST: Add test for object method (and general unary) loops +* `#15042 <https://github.com/numpy/numpy/pull/15042>`__: REL: Update master after 1.18.x branch. +* `#15043 <https://github.com/numpy/numpy/pull/15043>`__: DOC: Update HOWTO_RELEASE.rst.txt +* `#15046 <https://github.com/numpy/numpy/pull/15046>`__: API, DOC: change names to multivariate_hypergeometric, improve... +* `#15050 <https://github.com/numpy/numpy/pull/15050>`__: DOC: Fix statement about norms +* `#15052 <https://github.com/numpy/numpy/pull/15052>`__: MAINT: follow-up cleanup for blas64 PR +* `#15054 <https://github.com/numpy/numpy/pull/15054>`__: DOC: add docstrings to refguide-check +* `#15066 <https://github.com/numpy/numpy/pull/15066>`__: Revert "DEP: issue deprecation warning when creating ragged array... +* `#15069 <https://github.com/numpy/numpy/pull/15069>`__: ENH: add support for ILP64 OpenBLAS (without symbol suffix) +* `#15070 <https://github.com/numpy/numpy/pull/15070>`__: DOC: correct version for NaT sort +* `#15072 <https://github.com/numpy/numpy/pull/15072>`__: TST: Check requires_memory immediately before the test +* `#15073 <https://github.com/numpy/numpy/pull/15073>`__: MAINT: core: Fix a very long line in the ufunc docstrings. +* `#15076 <https://github.com/numpy/numpy/pull/15076>`__: BUG: test, fix flexible dtype conversion on class with __array__ +* `#15082 <https://github.com/numpy/numpy/pull/15082>`__: TST: add value to pytest.ini for pytest6 compatibility +* `#15085 <https://github.com/numpy/numpy/pull/15085>`__: MAINT: Ragged cleanup +* `#15097 <https://github.com/numpy/numpy/pull/15097>`__: DOC: bring the out parameter docstring into line with ufuncs +* `#15106 <https://github.com/numpy/numpy/pull/15106>`__: ENH: f2py: add --f2cmap option for specifying the name of .f2py_f2cmap +* `#15107 <https://github.com/numpy/numpy/pull/15107>`__: TST: add BLAS ILP64 run in Travis & Azure +* `#15110 <https://github.com/numpy/numpy/pull/15110>`__: MAINT: Fix expm1 instability for small complex numbers. +* `#15115 <https://github.com/numpy/numpy/pull/15115>`__: MAINT: random: Remove a few unused imports from test files. +* `#15116 <https://github.com/numpy/numpy/pull/15116>`__: MAINT: Bump pytest from 5.3.1 to 5.3.2 +* `#15118 <https://github.com/numpy/numpy/pull/15118>`__: API: remove undocumented use of __array__(dtype, context) +* `#15120 <https://github.com/numpy/numpy/pull/15120>`__: MAINT,CI: fix signed-unsigned comparison warning +* `#15124 <https://github.com/numpy/numpy/pull/15124>`__: DOC: Update documentation of np.clip +* `#15125 <https://github.com/numpy/numpy/pull/15125>`__: DOC: Remove reference to basic RNG +* `#15126 <https://github.com/numpy/numpy/pull/15126>`__: MAINT: Fix randint 0d limits and other 0d cleanups +* `#15129 <https://github.com/numpy/numpy/pull/15129>`__: DOC: Fix typos, via a Levenshtein-style corrector +* `#15133 <https://github.com/numpy/numpy/pull/15133>`__: MAINT: CI: Clean up .travis.yml +* `#15136 <https://github.com/numpy/numpy/pull/15136>`__: DOC: Correct choice signature +* `#15138 <https://github.com/numpy/numpy/pull/15138>`__: DOC: Correct documentation in choice +* `#15143 <https://github.com/numpy/numpy/pull/15143>`__: TST: shippable build efficiency +* `#15144 <https://github.com/numpy/numpy/pull/15144>`__: BUG: ensure reduction output matches input along non-reduction... +* `#15149 <https://github.com/numpy/numpy/pull/15149>`__: REL: Update master after NumPy 1.18.0 release. +* `#15150 <https://github.com/numpy/numpy/pull/15150>`__: MAINT: Update pavement.py for towncrier. +* `#15153 <https://github.com/numpy/numpy/pull/15153>`__: DOC: update cholesky docstring regarding input checking +* `#15154 <https://github.com/numpy/numpy/pull/15154>`__: DOC: update documentation on how to build NumPy +* `#15156 <https://github.com/numpy/numpy/pull/15156>`__: DOC: add moved modules to 1.18 release note +* `#15160 <https://github.com/numpy/numpy/pull/15160>`__: MAINT: Update required cython version to 0.29.14. +* `#15164 <https://github.com/numpy/numpy/pull/15164>`__: BUG: searchsorted: passing the keys as a keyword argument +* `#15170 <https://github.com/numpy/numpy/pull/15170>`__: BUG: use tmp dir and check version for cython test +* `#15178 <https://github.com/numpy/numpy/pull/15178>`__: TST: improve assert message of assert_array_max_ulp +* `#15187 <https://github.com/numpy/numpy/pull/15187>`__: MAINT: unskip test on win32 +* `#15189 <https://github.com/numpy/numpy/pull/15189>`__: ENH: Add property-based tests using Hypothesis +* `#15194 <https://github.com/numpy/numpy/pull/15194>`__: BUG: test, fix for c++ compilation +* `#15196 <https://github.com/numpy/numpy/pull/15196>`__: DOC: Adding instructions for building documentation to developer... +* `#15197 <https://github.com/numpy/numpy/pull/15197>`__: DOC: NEP 37: A dispatch protocol for NumPy-like modules +* `#15203 <https://github.com/numpy/numpy/pull/15203>`__: MAINT: Do not use private Python function in testing +* `#15205 <https://github.com/numpy/numpy/pull/15205>`__: DOC: Improvements to Quickstart Tutorial. +* `#15211 <https://github.com/numpy/numpy/pull/15211>`__: BUG: distutils: fix msvc+gfortran openblas handling corner case +* `#15212 <https://github.com/numpy/numpy/pull/15212>`__: BUG: lib: Fix handling of integer arrays by gradient. +* `#15215 <https://github.com/numpy/numpy/pull/15215>`__: MAINT: lib: A little bit of clean up for the new year. +* `#15216 <https://github.com/numpy/numpy/pull/15216>`__: REL: Update master after NumPy 1.16.6 and 1.17.5 releases. +* `#15217 <https://github.com/numpy/numpy/pull/15217>`__: DEP: records: Deprecate treating shape=0 as shape=None +* `#15218 <https://github.com/numpy/numpy/pull/15218>`__: ENH: build fallback lapack_lite with 64-bit integers on 64-bit... +* `#15224 <https://github.com/numpy/numpy/pull/15224>`__: MAINT: linalg: use symbol suffix in fallback lapack_lite +* `#15227 <https://github.com/numpy/numpy/pull/15227>`__: DOC: typo in release.rst +* `#15228 <https://github.com/numpy/numpy/pull/15228>`__: NEP: universal SIMD NEP 38 +* `#15229 <https://github.com/numpy/numpy/pull/15229>`__: MAINT: Remove unused int_asbuffer +* `#15232 <https://github.com/numpy/numpy/pull/15232>`__: MAINT: Cleaning up PY_MAJOR_VERSION/PY_VERSION_HEX +* `#15233 <https://github.com/numpy/numpy/pull/15233>`__: MAINT: Clean up more PY_VERSION_HEX +* `#15236 <https://github.com/numpy/numpy/pull/15236>`__: MAINT: Remove implicit inheritance from object class +* `#15238 <https://github.com/numpy/numpy/pull/15238>`__: MAINT: only add --std=c99 where needed +* `#15239 <https://github.com/numpy/numpy/pull/15239>`__: MAINT: Remove Python2 newbuffer getbuffer +* `#15240 <https://github.com/numpy/numpy/pull/15240>`__: MAINT: Py3K array_as_buffer and gentype_as_buffer +* `#15241 <https://github.com/numpy/numpy/pull/15241>`__: MAINT: Remove references to non-existent sys.exc_clear() +* `#15242 <https://github.com/numpy/numpy/pull/15242>`__: DOC: Update HOWTO_RELEASE.rst +* `#15248 <https://github.com/numpy/numpy/pull/15248>`__: MAINT: cleanup use of sys.exc_info +* `#15249 <https://github.com/numpy/numpy/pull/15249>`__: MAINT: Eliminate some calls to `eval` +* `#15251 <https://github.com/numpy/numpy/pull/15251>`__: MAINT: Improve const-correctness of shapes and strides +* `#15253 <https://github.com/numpy/numpy/pull/15253>`__: DOC: clarify the effect of None parameters passed to ndarray.view +* `#15254 <https://github.com/numpy/numpy/pull/15254>`__: MAINT: Improve const-correctness of string arguments +* `#15255 <https://github.com/numpy/numpy/pull/15255>`__: MAINT: Delete numpy.distutils.compat +* `#15256 <https://github.com/numpy/numpy/pull/15256>`__: MAINT: Implement keyword-only arguments as syntax +* `#15260 <https://github.com/numpy/numpy/pull/15260>`__: MAINT: Remove FIXME comments introduced in the previous commit +* `#15261 <https://github.com/numpy/numpy/pull/15261>`__: MAINT: Work with unicode strings in `dtype('i8,i8')` +* `#15262 <https://github.com/numpy/numpy/pull/15262>`__: BUG: Use PyDict_GetItemWithError() instead of PyDict_GetItem() +* `#15263 <https://github.com/numpy/numpy/pull/15263>`__: MAINT: Remove python2 array_{get,set}slice +* `#15264 <https://github.com/numpy/numpy/pull/15264>`__: DOC: Add some missing functions in the list of available ufuncs. +* `#15265 <https://github.com/numpy/numpy/pull/15265>`__: MAINT: Tidy PyArray_DescrConverter +* `#15266 <https://github.com/numpy/numpy/pull/15266>`__: MAINT: remove duplicated if statements between DescrConverters +* `#15267 <https://github.com/numpy/numpy/pull/15267>`__: BUG: Fix PyArray_DescrAlignConverter2 on tuples +* `#15268 <https://github.com/numpy/numpy/pull/15268>`__: MAINT: Remove Python2 ndarray.__unicode__ +* `#15272 <https://github.com/numpy/numpy/pull/15272>`__: MAINT: Remove Python 2 divide +* `#15273 <https://github.com/numpy/numpy/pull/15273>`__: MAINT: minor formatting fixups for NEP-37 +* `#15274 <https://github.com/numpy/numpy/pull/15274>`__: MAINT: Post NumPy 1.18.1 update. +* `#15275 <https://github.com/numpy/numpy/pull/15275>`__: MAINT: travis-ci: Update CI scripts. +* `#15278 <https://github.com/numpy/numpy/pull/15278>`__: BENCH: Add benchmark for small array coercions +* `#15279 <https://github.com/numpy/numpy/pull/15279>`__: BUILD: use standard build of OpenBLAS for aarch64, ppc64le, s390x +* `#15280 <https://github.com/numpy/numpy/pull/15280>`__: BENCH: Add basic benchmarks for take and putmask +* `#15281 <https://github.com/numpy/numpy/pull/15281>`__: MAINT: Cleanup most PY3K #ifdef guards +* `#15282 <https://github.com/numpy/numpy/pull/15282>`__: DOC: BLD: add empty release notes for 1.19.0 to fix doc build... +* `#15284 <https://github.com/numpy/numpy/pull/15284>`__: MAINT: Use a simpler return convention for internal functions +* `#15285 <https://github.com/numpy/numpy/pull/15285>`__: MAINT: Simplify np.int_ inheritance +* `#15286 <https://github.com/numpy/numpy/pull/15286>`__: DOC" Update np.full docstring. +* `#15287 <https://github.com/numpy/numpy/pull/15287>`__: MAINT: Express PyArray_DescrAlignConverter in terms of _convert_from_any +* `#15288 <https://github.com/numpy/numpy/pull/15288>`__: MAINT: Push down declarations in _convert_from_* +* `#15289 <https://github.com/numpy/numpy/pull/15289>`__: MAINT: C code simplifications +* `#15291 <https://github.com/numpy/numpy/pull/15291>`__: BUG: Add missing error handling to _convert_from_list +* `#15295 <https://github.com/numpy/numpy/pull/15295>`__: DOC: Added tutorial about linear algebra on multidimensional... +* `#15300 <https://github.com/numpy/numpy/pull/15300>`__: MAINT: Refactor dtype conversion functions to be more similar +* `#15303 <https://github.com/numpy/numpy/pull/15303>`__: DOC: Updating f2py docs to python 3 and fixing some typos +* `#15304 <https://github.com/numpy/numpy/pull/15304>`__: MAINT: Remove NPY_PY3K constant +* `#15305 <https://github.com/numpy/numpy/pull/15305>`__: MAINT: Remove sys.version checks in tests +* `#15307 <https://github.com/numpy/numpy/pull/15307>`__: MAINT: cleanup sys.version dependant code +* `#15310 <https://github.com/numpy/numpy/pull/15310>`__: MAINT: Ensure `_convert_from_*` functions set errors +* `#15312 <https://github.com/numpy/numpy/pull/15312>`__: MAINT: Avoid escaping unicode in error messages +* `#15315 <https://github.com/numpy/numpy/pull/15315>`__: MAINT: Change file extension of ma README to rst. +* `#15319 <https://github.com/numpy/numpy/pull/15319>`__: BUG: fix NameError in clip nan propagation tests +* `#15323 <https://github.com/numpy/numpy/pull/15323>`__: NEP: document reimplementation of NEP 34 +* `#15324 <https://github.com/numpy/numpy/pull/15324>`__: MAINT: fix typos +* `#15328 <https://github.com/numpy/numpy/pull/15328>`__: TST: move pypy CI to ubuntu 18.04 +* `#15329 <https://github.com/numpy/numpy/pull/15329>`__: TST: move _no_tracing to testing._private, remove testing.support +* `#15333 <https://github.com/numpy/numpy/pull/15333>`__: BUG: Add some missing C error handling +* `#15335 <https://github.com/numpy/numpy/pull/15335>`__: MAINT: Remove sys.version checks +* `#15336 <https://github.com/numpy/numpy/pull/15336>`__: DEP: Deprecate `->f->fastclip` at registration time +* `#15338 <https://github.com/numpy/numpy/pull/15338>`__: DOC: document site.cfg.example +* `#15350 <https://github.com/numpy/numpy/pull/15350>`__: MAINT: Fix mistype in histogramdd docstring +* `#15351 <https://github.com/numpy/numpy/pull/15351>`__: DOC, BLD: reword release note, upgrade sphinx version +* `#15353 <https://github.com/numpy/numpy/pull/15353>`__: MAINT: Remove unnecessary calls to PyArray_DATA from binomial... +* `#15354 <https://github.com/numpy/numpy/pull/15354>`__: MAINT: Bump pytest from 5.3.2 to 5.3.3 +* `#15358 <https://github.com/numpy/numpy/pull/15358>`__: MAINT: Remove six +* `#15361 <https://github.com/numpy/numpy/pull/15361>`__: MAINT: Revise imports from collections.abc module +* `#15362 <https://github.com/numpy/numpy/pull/15362>`__: MAINT: remove internal functions required to handle Python2/3... +* `#15364 <https://github.com/numpy/numpy/pull/15364>`__: MAINT: Remove other uses of six module +* `#15366 <https://github.com/numpy/numpy/pull/15366>`__: MAINT: resolve pyflake F403 'from module import *' used +* `#15368 <https://github.com/numpy/numpy/pull/15368>`__: MAINT: Update tox for supported Python versions +* `#15369 <https://github.com/numpy/numpy/pull/15369>`__: MAINT: simd: Avoid signed comparison warning +* `#15370 <https://github.com/numpy/numpy/pull/15370>`__: DOC: Updating Chararry Buffer datatypes #15360 +* `#15374 <https://github.com/numpy/numpy/pull/15374>`__: TST: Simplify unicode test +* `#15375 <https://github.com/numpy/numpy/pull/15375>`__: MAINT: Use `with open` when possible +* `#15377 <https://github.com/numpy/numpy/pull/15377>`__: MAINT: Cleanup python2 references +* `#15379 <https://github.com/numpy/numpy/pull/15379>`__: MAINT: Python2 Cleanups +* `#15381 <https://github.com/numpy/numpy/pull/15381>`__: DEP: add PendingDeprecation to matlib.py funky namespace +* `#15385 <https://github.com/numpy/numpy/pull/15385>`__: BUG, MAINT: Stop using the error-prone deprecated Py_UNICODE... +* `#15386 <https://github.com/numpy/numpy/pull/15386>`__: MAINT: clean up some macros in scalarapi.c +* `#15393 <https://github.com/numpy/numpy/pull/15393>`__: MAINT/BUG: Fixups to scalar base classes +* `#15397 <https://github.com/numpy/numpy/pull/15397>`__: BUG: np.load does not handle empty array with an empty descr +* `#15398 <https://github.com/numpy/numpy/pull/15398>`__: MAINT: Revise imports from urllib modules +* `#15399 <https://github.com/numpy/numpy/pull/15399>`__: MAINT: Remove Python3 DeprecationWarning from pytest.ini +* `#15400 <https://github.com/numpy/numpy/pull/15400>`__: MAINT: cleanup _pytesttester.py +* `#15401 <https://github.com/numpy/numpy/pull/15401>`__: BUG: Flags should not contain spaces +* `#15403 <https://github.com/numpy/numpy/pull/15403>`__: MAINT: Clean up, mostly unused imports. +* `#15405 <https://github.com/numpy/numpy/pull/15405>`__: BUG/TEST: core: Fix an undefined name in a test. +* `#15407 <https://github.com/numpy/numpy/pull/15407>`__: MAINT: Replace basestring with str. +* `#15408 <https://github.com/numpy/numpy/pull/15408>`__: ENH: Use AVX-512F for complex number arithmetic, absolute, square... +* `#15414 <https://github.com/numpy/numpy/pull/15414>`__: MAINT: Remove Python2 workarounds +* `#15417 <https://github.com/numpy/numpy/pull/15417>`__: MAINT: Cleanup references to python2 +* `#15418 <https://github.com/numpy/numpy/pull/15418>`__: MAINT, DOC: Remove use of old Python __builtin__, now known as... +* `#15421 <https://github.com/numpy/numpy/pull/15421>`__: ENH: Make use of ExitStack in npyio.py +* `#15422 <https://github.com/numpy/numpy/pull/15422>`__: MAINT: Inline gentype_getreadbuf +* `#15423 <https://github.com/numpy/numpy/pull/15423>`__: MAINT: Use f-strings for clarity. +* `#15427 <https://github.com/numpy/numpy/pull/15427>`__: DEP: Schedule unused C-API functions for removal/disabling +* `#15428 <https://github.com/numpy/numpy/pull/15428>`__: DOC: Improve ndarray.ctypes example +* `#15429 <https://github.com/numpy/numpy/pull/15429>`__: DOC: distutils: Add a docstring to show_config(). +* `#15430 <https://github.com/numpy/numpy/pull/15430>`__: MAINT: Use contextmanager in _run_doctests +* `#15434 <https://github.com/numpy/numpy/pull/15434>`__: MAINT: Updated polynomial to use fstrings +* `#15435 <https://github.com/numpy/numpy/pull/15435>`__: DOC: Fix Incorrect document in Beginner Docs +* `#15436 <https://github.com/numpy/numpy/pull/15436>`__: MAINT: Update core.py with fstrings (issue #15420) +* `#15439 <https://github.com/numpy/numpy/pull/15439>`__: DOC: fix docstrings so `python tools/refguide-check --rst <file>... +* `#15441 <https://github.com/numpy/numpy/pull/15441>`__: MAINT: Tidy macros in scalar_new +* `#15444 <https://github.com/numpy/numpy/pull/15444>`__: MAINT: use 'yield from <expr>' for simple cases +* `#15445 <https://github.com/numpy/numpy/pull/15445>`__: MAINT: Bump pytest from 5.3.3 to 5.3.4 +* `#15446 <https://github.com/numpy/numpy/pull/15446>`__: BUG: Reject nonsense arguments to scalar constructors +* `#15449 <https://github.com/numpy/numpy/pull/15449>`__: DOC: Update refguide_check note on how to skip code +* `#15451 <https://github.com/numpy/numpy/pull/15451>`__: MAINT: Simplify `np.object_.__new__` +* `#15452 <https://github.com/numpy/numpy/pull/15452>`__: STY,MAINT: avoid 'multiple imports on one line' +* `#15464 <https://github.com/numpy/numpy/pull/15464>`__: MAINT: Cleanup duplicate line in refguide_check +* `#15465 <https://github.com/numpy/numpy/pull/15465>`__: MAINT: cleanup unused imports; avoid redefinition of imports +* `#15468 <https://github.com/numpy/numpy/pull/15468>`__: BUG: Fix for SVD not always sorted with hermitian=True +* `#15469 <https://github.com/numpy/numpy/pull/15469>`__: MAINT: Simplify scalar __new__ some more +* `#15474 <https://github.com/numpy/numpy/pull/15474>`__: MAINT: Eliminate messy _WORK macro +* `#15476 <https://github.com/numpy/numpy/pull/15476>`__: update result of rng.random(3) to current rng output +* `#15480 <https://github.com/numpy/numpy/pull/15480>`__: DOC: Correct get_state doc +* `#15482 <https://github.com/numpy/numpy/pull/15482>`__: MAINT: Use `.identifier = val` to fill type structs +* `#15483 <https://github.com/numpy/numpy/pull/15483>`__: [DOC] Mention behaviour of np.squeeze with one element +* `#15484 <https://github.com/numpy/numpy/pull/15484>`__: ENH: fixing generic error messages to be more specific in multiarray/descriptor.c +* `#15487 <https://github.com/numpy/numpy/pull/15487>`__: BUG: Fixing result of np quantile edge case +* `#15491 <https://github.com/numpy/numpy/pull/15491>`__: TST: mark the top 3 slowest tests to save ~10 seconds +* `#15493 <https://github.com/numpy/numpy/pull/15493>`__: MAINT: Bump pytest from 5.3.4 to 5.3.5 +* `#15500 <https://github.com/numpy/numpy/pull/15500>`__: MAINT: Use True/False instead of 1/0 in np.dtype.__reduce__ +* `#15503 <https://github.com/numpy/numpy/pull/15503>`__: MAINT: Do not allow `copyswap` and friends to fail silently +* `#15504 <https://github.com/numpy/numpy/pull/15504>`__: DOC: Remove duplicated code in true_divide docstring +* `#15505 <https://github.com/numpy/numpy/pull/15505>`__: NEP 40: Informational NEP about current DTypes +* `#15510 <https://github.com/numpy/numpy/pull/15510>`__: DOC: Update unique docstring example +* `#15511 <https://github.com/numpy/numpy/pull/15511>`__: MAINT: Large overhead in some random functions +* `#15516 <https://github.com/numpy/numpy/pull/15516>`__: TST: Fix missing output in refguide-check +* `#15521 <https://github.com/numpy/numpy/pull/15521>`__: MAINT: Simplify arraydescr_richcompare +* `#15522 <https://github.com/numpy/numpy/pull/15522>`__: MAINT: Fix internal misuses of `NPY_TITLE_KEY` +* `#15524 <https://github.com/numpy/numpy/pull/15524>`__: DOC: Update instructions for building/archiving docs. +* `#15526 <https://github.com/numpy/numpy/pull/15526>`__: BUG: Fix inline assembly that detects cpu features on x86(32bit) +* `#15532 <https://github.com/numpy/numpy/pull/15532>`__: update doctests, small bugs and changes of repr +* `#15534 <https://github.com/numpy/numpy/pull/15534>`__: DEP: Do not allow "abstract" dtype conversion/creation +* `#15536 <https://github.com/numpy/numpy/pull/15536>`__: DOC: Minor copyediting on NEP 37. +* `#15538 <https://github.com/numpy/numpy/pull/15538>`__: MAINT: Extract repeated code to a helper function +* `#15543 <https://github.com/numpy/numpy/pull/15543>`__: NEP: edit and move NEP 38 to accepted status +* `#15547 <https://github.com/numpy/numpy/pull/15547>`__: MAINT: Refresh Doxyfile and modernize numpyfilter.py +* `#15549 <https://github.com/numpy/numpy/pull/15549>`__: TST: Accuracy test float32 sin/cos/exp/log for AVX platforms +* `#15550 <https://github.com/numpy/numpy/pull/15550>`__: DOC: Improve the `numpy.linalg.eig` docstring. +* `#15554 <https://github.com/numpy/numpy/pull/15554>`__: NEP 44 - Restructuring the NumPy Documentation +* `#15556 <https://github.com/numpy/numpy/pull/15556>`__: TST: (Travis CI) Use full python3-dbg path for virtual env creation +* `#15560 <https://github.com/numpy/numpy/pull/15560>`__: BUG, DOC: restore missing import +* `#15566 <https://github.com/numpy/numpy/pull/15566>`__: DOC: Removing bad practices from quick start + some PEP8 +* `#15574 <https://github.com/numpy/numpy/pull/15574>`__: TST: Do not create symbolic link named gfortran. +* `#15575 <https://github.com/numpy/numpy/pull/15575>`__: DOC: Document caveat in random.uniform +* `#15579 <https://github.com/numpy/numpy/pull/15579>`__: DOC: numpy.clip is equivalent to minimum(..., maximum(...)) +* `#15582 <https://github.com/numpy/numpy/pull/15582>`__: MAINT: Bump cython from 0.29.14 to 0.29.15 +* `#15583 <https://github.com/numpy/numpy/pull/15583>`__: MAINT: Bump hypothesis from 5.3.0 to 5.5.4 +* `#15585 <https://github.com/numpy/numpy/pull/15585>`__: BLD: manylinux2010 docker reports machine=i686 +* `#15598 <https://github.com/numpy/numpy/pull/15598>`__: BUG: Ignore differences in NAN for computing ULP differences +* `#15600 <https://github.com/numpy/numpy/pull/15600>`__: TST: use manylinux2010 docker instead of ubuntu +* `#15610 <https://github.com/numpy/numpy/pull/15610>`__: TST: mask DeprecationWarning in xfailed test +* `#15612 <https://github.com/numpy/numpy/pull/15612>`__: BUG: Fix bug in AVX-512F np.maximum and np.minimum +* `#15615 <https://github.com/numpy/numpy/pull/15615>`__: BUG: Remove check requiring natural alignment of float/double... +* `#15616 <https://github.com/numpy/numpy/pull/15616>`__: DOC: Add missing imports, definitions and dummy file +* `#15619 <https://github.com/numpy/numpy/pull/15619>`__: DOC: Fix documentation for apply_along_axis +* `#15624 <https://github.com/numpy/numpy/pull/15624>`__: DOC: fix printing, np., deprecation for refguide +* `#15631 <https://github.com/numpy/numpy/pull/15631>`__: MAINT: Pull identical line out of conditional. +* `#15633 <https://github.com/numpy/numpy/pull/15633>`__: DOC: remove broken link in f2py tutorial +* `#15639 <https://github.com/numpy/numpy/pull/15639>`__: BLD: update openblas download to new location, use manylinux2010-base +* `#15648 <https://github.com/numpy/numpy/pull/15648>`__: MAINT: AVX512 implementation with intrinsic for float64 input... +* `#15653 <https://github.com/numpy/numpy/pull/15653>`__: BLD: update OpenBLAS to pre-0.3.9 version +* `#15662 <https://github.com/numpy/numpy/pull/15662>`__: DOC: Refactor `np.polynomial` docs using `automodule` +* `#15665 <https://github.com/numpy/numpy/pull/15665>`__: BUG: fix doctest exception messages +* `#15672 <https://github.com/numpy/numpy/pull/15672>`__: MAINT: Added comment pointing FIXME to relevant PR. +* `#15673 <https://github.com/numpy/numpy/pull/15673>`__: DOC: Make extension module wording more clear +* `#15678 <https://github.com/numpy/numpy/pull/15678>`__: DOC: Improve np.finfo docs +* `#15680 <https://github.com/numpy/numpy/pull/15680>`__: DOC: Improve Benchmark README with environment setup and more... +* `#15682 <https://github.com/numpy/numpy/pull/15682>`__: MAINT: Bump hypothesis from 5.5.4 to 5.6.0 +* `#15683 <https://github.com/numpy/numpy/pull/15683>`__: NEP: move NEP 44 to accepted status +* `#15694 <https://github.com/numpy/numpy/pull/15694>`__: DOC: Fix indexing docs to pass refguide +* `#15695 <https://github.com/numpy/numpy/pull/15695>`__: MAINT: Test during import to detect bugs with Accelerate(MacOS)... +* `#15696 <https://github.com/numpy/numpy/pull/15696>`__: MAINT: Add a fast path to var for complex input +* `#15701 <https://github.com/numpy/numpy/pull/15701>`__: MAINT: Convert shebang from python to python3 (#15687) +* `#15702 <https://github.com/numpy/numpy/pull/15702>`__: MAINT: replace optparse with argparse for 'doc' and 'tools' scripts +* `#15703 <https://github.com/numpy/numpy/pull/15703>`__: DOC: Fix quickstart doc to pass refguide +* `#15706 <https://github.com/numpy/numpy/pull/15706>`__: MAINT: Fixing typos in f2py comments and code. +* `#15710 <https://github.com/numpy/numpy/pull/15710>`__: DOC: fix SVD tutorial to pass refguide +* `#15714 <https://github.com/numpy/numpy/pull/15714>`__: MAINT: use list-based APIs to call subprocesses +* `#15715 <https://github.com/numpy/numpy/pull/15715>`__: ENH: update numpy.linalg.multi_dot to accept an `out` argument +* `#15716 <https://github.com/numpy/numpy/pull/15716>`__: TST: always use 'python -mpip' not 'pip' +* `#15717 <https://github.com/numpy/numpy/pull/15717>`__: DOC: update datetime reference to pass refguide +* `#15718 <https://github.com/numpy/numpy/pull/15718>`__: DOC: Fix coremath.rst to fix refguide_check +* `#15720 <https://github.com/numpy/numpy/pull/15720>`__: DOC: fix remaining doc files for refguide_check +* `#15723 <https://github.com/numpy/numpy/pull/15723>`__: BUG: fix logic error when nm fails on 32-bit +* `#15724 <https://github.com/numpy/numpy/pull/15724>`__: TST: Remove nose from the test_requirements.txt file. +* `#15733 <https://github.com/numpy/numpy/pull/15733>`__: DOC: Allow NEPs to link to python, numpy, scipy, and matplotlib... +* `#15736 <https://github.com/numpy/numpy/pull/15736>`__: BUG: Guarantee array is in valid state after memory error occurs... +* `#15738 <https://github.com/numpy/numpy/pull/15738>`__: MAINT: Remove non-native byte order from _var check. +* `#15740 <https://github.com/numpy/numpy/pull/15740>`__: MAINT: Add better error handling in linalg.norm for vectors and... +* `#15745 <https://github.com/numpy/numpy/pull/15745>`__: MAINT: doc: Remove doc/summarize.py +* `#15747 <https://github.com/numpy/numpy/pull/15747>`__: BUG: lib: Handle axes with length 0 in np.unique. +* `#15749 <https://github.com/numpy/numpy/pull/15749>`__: DOC: document inconsistency between the shape of data and mask... +* `#15750 <https://github.com/numpy/numpy/pull/15750>`__: BUG, TST: fix f2py for PyPy, skip one test for PyPy +* `#15752 <https://github.com/numpy/numpy/pull/15752>`__: MAINT: Fix swig tests issue #15743 +* `#15757 <https://github.com/numpy/numpy/pull/15757>`__: MAINT: CI: Add an explicit 'pr' section to azure-pipelines.yml +* `#15762 <https://github.com/numpy/numpy/pull/15762>`__: MAINT: Bump pytest from 5.3.5 to 5.4.1 +* `#15766 <https://github.com/numpy/numpy/pull/15766>`__: BUG,MAINT: Remove incorrect special case in string to number... +* `#15768 <https://github.com/numpy/numpy/pull/15768>`__: REL: Update master after 1.18.2 release. +* `#15769 <https://github.com/numpy/numpy/pull/15769>`__: ENH: Allow toggling madvise hugepage and fix default +* `#15771 <https://github.com/numpy/numpy/pull/15771>`__: DOC: Fix runtests example in developer docs +* `#15773 <https://github.com/numpy/numpy/pull/15773>`__: DEP: Make issubdtype consistent for types and dtypes +* `#15774 <https://github.com/numpy/numpy/pull/15774>`__: MAINT: remove useless `global` statements +* `#15778 <https://github.com/numpy/numpy/pull/15778>`__: BLD: Add requirements.txt file for building docs +* `#15781 <https://github.com/numpy/numpy/pull/15781>`__: BUG: don't add 'public' or 'private' if the other one exists +* `#15784 <https://github.com/numpy/numpy/pull/15784>`__: ENH: Use TypeError in `np.array` for python consistency +* `#15794 <https://github.com/numpy/numpy/pull/15794>`__: BUG: Add basic __format__ for masked element to fix incorrect... +* `#15797 <https://github.com/numpy/numpy/pull/15797>`__: TST: Add unit test for out=None of np.einsum +* `#15799 <https://github.com/numpy/numpy/pull/15799>`__: MAINT: Cleanups to np.insert and np.delete +* `#15800 <https://github.com/numpy/numpy/pull/15800>`__: BUG: Add error-checking versions of strided casts. +* `#15802 <https://github.com/numpy/numpy/pull/15802>`__: DEP: Make `np.insert` and `np.delete` on 0d arrays with an axis... +* `#15803 <https://github.com/numpy/numpy/pull/15803>`__: DOC: correct possible list lengths for `extobj` in ufunc calls +* `#15804 <https://github.com/numpy/numpy/pull/15804>`__: DEP: Make np.delete on out-of-bounds indices an error +* `#15805 <https://github.com/numpy/numpy/pull/15805>`__: DEP: Forbid passing non-integral index arrays to `insert` and... +* `#15806 <https://github.com/numpy/numpy/pull/15806>`__: TST: Parametrize sort test +* `#15809 <https://github.com/numpy/numpy/pull/15809>`__: TST: switch PyPy job with CPython +* `#15812 <https://github.com/numpy/numpy/pull/15812>`__: TST: Remove code that is not supposed to warn out of warning... +* `#15815 <https://github.com/numpy/numpy/pull/15815>`__: DEP: Do not cast boolean indices to integers in np.delete +* `#15816 <https://github.com/numpy/numpy/pull/15816>`__: MAINT: simplify code that assumes str/unicode and int/long are... +* `#15830 <https://github.com/numpy/numpy/pull/15830>`__: MAINT: pathlib and hashlib are in stdlib in Python 3.5+ +* `#15832 <https://github.com/numpy/numpy/pull/15832>`__: ENH: improved error message `IndexError: too many indices for... +* `#15836 <https://github.com/numpy/numpy/pull/15836>`__: BUG: Fix IndexError for illegal axis in np.mean +* `#15839 <https://github.com/numpy/numpy/pull/15839>`__: DOC: Minor fix to _hist_bin_fd documentation +* `#15840 <https://github.com/numpy/numpy/pull/15840>`__: BUG,DEP: Make `scalar.__round__()` behave like pythons round +* `#15843 <https://github.com/numpy/numpy/pull/15843>`__: DOC: First steps towards docs restructuring (NEP 44) +* `#15848 <https://github.com/numpy/numpy/pull/15848>`__: DOC, TST: enable refguide_check in circleci +* `#15850 <https://github.com/numpy/numpy/pull/15850>`__: DOC: fix typo in C-API reference +* `#15854 <https://github.com/numpy/numpy/pull/15854>`__: DOC: Fix docstring for _hist_bin_auto. +* `#15866 <https://github.com/numpy/numpy/pull/15866>`__: MAINT: Bump cython from 0.29.15 to 0.29.16 +* `#15867 <https://github.com/numpy/numpy/pull/15867>`__: DEP: Deprecate ndarray.tostring() +* `#15868 <https://github.com/numpy/numpy/pull/15868>`__: TST: use draft OpenBLAS build +* `#15872 <https://github.com/numpy/numpy/pull/15872>`__: BUG: Fix eigh and cholesky methods of numpy.random.multivariate_normal +* `#15876 <https://github.com/numpy/numpy/pull/15876>`__: BUG: Check that `pvals` is 1D in `_generator.multinomial`. +* `#15877 <https://github.com/numpy/numpy/pull/15877>`__: DOC: Add missing signature from nditer docstring +* `#15881 <https://github.com/numpy/numpy/pull/15881>`__: BUG: Fix empty_like to respect shape=() +* `#15882 <https://github.com/numpy/numpy/pull/15882>`__: BUG: Do not ignore empty tuple of strides in ndarray.__new__ +* `#15883 <https://github.com/numpy/numpy/pull/15883>`__: MAINT: Remove duplicated code in iotools.py +* `#15884 <https://github.com/numpy/numpy/pull/15884>`__: BUG: Setting a 0d array's strides to themselves should be legal +* `#15885 <https://github.com/numpy/numpy/pull/15885>`__: BUG: Respect itershape=() in nditer +* `#15887 <https://github.com/numpy/numpy/pull/15887>`__: MAINT: Clean-up 'next = __next__' used for Python 2 compatibility +* `#15893 <https://github.com/numpy/numpy/pull/15893>`__: TST: Run test_large_zip in a child process +* `#15894 <https://github.com/numpy/numpy/pull/15894>`__: DOC: Add missing doc of numpy.ma.apply_over_axes in API list. +* `#15899 <https://github.com/numpy/numpy/pull/15899>`__: DOC: Improve record module documentation +* `#15901 <https://github.com/numpy/numpy/pull/15901>`__: DOC: Fixed order of items and link to mailing list in dev docs... +* `#15903 <https://github.com/numpy/numpy/pull/15903>`__: BLD: report clang version on macOS +* `#15904 <https://github.com/numpy/numpy/pull/15904>`__: MAINT: records: Remove private `format_parser._descr` attribute +* `#15914 <https://github.com/numpy/numpy/pull/15914>`__: BUG: random: Disallow p=0 in negative_binomial +* `#15921 <https://github.com/numpy/numpy/pull/15921>`__: ENH: Use sysconfig instead of probing Makefile +* `#15928 <https://github.com/numpy/numpy/pull/15928>`__: DOC: Update np.copy docstring to include ragged case +* `#15931 <https://github.com/numpy/numpy/pull/15931>`__: DOC: Correct private function name to PyArray_AdaptFlexibleDType +* `#15936 <https://github.com/numpy/numpy/pull/15936>`__: MAINT: Fix capitalization in error message in `mtrand.pyx` +* `#15939 <https://github.com/numpy/numpy/pull/15939>`__: DOC: Update np.rollaxis docstring +* `#15949 <https://github.com/numpy/numpy/pull/15949>`__: BUG: fix AttributeError on accessing object in nested MaskedArray. +* `#15951 <https://github.com/numpy/numpy/pull/15951>`__: BUG: Alpha parameter must be 1D in `generator.dirichlet` +* `#15953 <https://github.com/numpy/numpy/pull/15953>`__: NEP: minor maintenance, update filename and fix a cross-reference +* `#15964 <https://github.com/numpy/numpy/pull/15964>`__: MAINT: Bump hypothesis from 5.8.0 to 5.8.3 +* `#15967 <https://github.com/numpy/numpy/pull/15967>`__: TST: Add slow_pypy support +* `#15968 <https://github.com/numpy/numpy/pull/15968>`__: DOC: Added note to angle function docstring about angle(0) being... +* `#15982 <https://github.com/numpy/numpy/pull/15982>`__: MAINT/BUG: Cleanup and minor fixes to conform_reduce_result +* `#15985 <https://github.com/numpy/numpy/pull/15985>`__: BUG: Avoid duplication in stack trace of `linspace(a, b, num=1.5)` +* `#15988 <https://github.com/numpy/numpy/pull/15988>`__: BUG: Fix inf and NaN-warnings in half float `nextafter` +* `#15989 <https://github.com/numpy/numpy/pull/15989>`__: MAINT: Remove 0d check for PyArray_ISONESEGMENT +* `#15990 <https://github.com/numpy/numpy/pull/15990>`__: DEV: Pass additional runtests.py args to ASV +* `#15993 <https://github.com/numpy/numpy/pull/15993>`__: DOC: Fix method documentation of function sort in MaskedArray +* `#16000 <https://github.com/numpy/numpy/pull/16000>`__: NEP: Improve Value Based Casting paragraph in NEP 40 +* `#16001 <https://github.com/numpy/numpy/pull/16001>`__: DOC: add note on flatten ordering in matlab page +* `#16007 <https://github.com/numpy/numpy/pull/16007>`__: TST: Add tests for the conversion utilities +* `#16008 <https://github.com/numpy/numpy/pull/16008>`__: BUG: Unify handling of string enum converters +* `#16009 <https://github.com/numpy/numpy/pull/16009>`__: MAINT: Replace npyiter_order_converter with PyArray_OrderConverter +* `#16010 <https://github.com/numpy/numpy/pull/16010>`__: BUG: Fix lexsort axis check +* `#16011 <https://github.com/numpy/numpy/pull/16011>`__: DOC: Clarify single-segment arrays in np reference +* `#16014 <https://github.com/numpy/numpy/pull/16014>`__: DOC: Change import error "howto" to link to new troubleshooting... +* `#16015 <https://github.com/numpy/numpy/pull/16015>`__: DOC: update first section of NEP 37 (``__array_function__`` downsides) +* `#16021 <https://github.com/numpy/numpy/pull/16021>`__: REL: Update master after 1.18.3 release. +* `#16024 <https://github.com/numpy/numpy/pull/16024>`__: MAINT: Bump hypothesis from 5.8.3 to 5.10.1 +* `#16025 <https://github.com/numpy/numpy/pull/16025>`__: DOC: initialise random number generator before first use in quickstart +* `#16032 <https://github.com/numpy/numpy/pull/16032>`__: ENH: Fix exception causes in build_clib.py +* `#16038 <https://github.com/numpy/numpy/pull/16038>`__: MAINT,TST: Move _repr_latex tests to test_printing. +* `#16041 <https://github.com/numpy/numpy/pull/16041>`__: BUG: missing 'f' prefix for fstring +* `#16042 <https://github.com/numpy/numpy/pull/16042>`__: ENH: Fix exception causes in build_ext.py +* `#16053 <https://github.com/numpy/numpy/pull/16053>`__: DOC: Small typo fixes to NEP 40. +* `#16054 <https://github.com/numpy/numpy/pull/16054>`__: DOC, BLD: update release howto and walkthrough for ananconda.org... +* `#16061 <https://github.com/numpy/numpy/pull/16061>`__: ENH: Chained exceptions in linalg.py and polyutils.py +* `#16064 <https://github.com/numpy/numpy/pull/16064>`__: MAINT: Chain exceptions in several places. +* `#16067 <https://github.com/numpy/numpy/pull/16067>`__: MAINT: Chain exceptions in memmap.py and core.py +* `#16068 <https://github.com/numpy/numpy/pull/16068>`__: BUG: Fix string to bool cast regression +* `#16069 <https://github.com/numpy/numpy/pull/16069>`__: DOC: Added page describing how to contribute to the docs team +* `#16075 <https://github.com/numpy/numpy/pull/16075>`__: DOC: add a note on sampling 2-D arrays to random.choice docstring +* `#16076 <https://github.com/numpy/numpy/pull/16076>`__: BUG: random: Generator.integers(2**32) always returned 0. +* `#16077 <https://github.com/numpy/numpy/pull/16077>`__: BLD: fix path to libgfortran on macOS +* `#16078 <https://github.com/numpy/numpy/pull/16078>`__: DOC: Add axis to random module "new or different" docs +* `#16079 <https://github.com/numpy/numpy/pull/16079>`__: DOC,BLD: Limit timeit iterations in random docs. +* `#16081 <https://github.com/numpy/numpy/pull/16081>`__: DOC: add note on type casting to numpy.left_shift(). +* `#16083 <https://github.com/numpy/numpy/pull/16083>`__: DOC: improve development debugging doc +* `#16084 <https://github.com/numpy/numpy/pull/16084>`__: DOC: tweak neps/scope.rst +* `#16085 <https://github.com/numpy/numpy/pull/16085>`__: MAINT: Bump cython from 0.29.16 to 0.29.17 +* `#16086 <https://github.com/numpy/numpy/pull/16086>`__: MAINT: Bump hypothesis from 5.10.1 to 5.10.4 +* `#16094 <https://github.com/numpy/numpy/pull/16094>`__: TST: use latest released PyPy instead of nightly builds +* `#16097 <https://github.com/numpy/numpy/pull/16097>`__: MAINT, DOC: Improve grammar on a comment in the quickstart +* `#16100 <https://github.com/numpy/numpy/pull/16100>`__: NEP 41: Accept NEP 41 and add DType<->scalar duplication paragraph +* `#16101 <https://github.com/numpy/numpy/pull/16101>`__: BLD: put openblas library in local directory on windows +* `#16113 <https://github.com/numpy/numpy/pull/16113>`__: MAINT: Fix random.PCG64 signature +* `#16119 <https://github.com/numpy/numpy/pull/16119>`__: DOC: Move misplaced news fragment for gh-13421 +* `#16122 <https://github.com/numpy/numpy/pull/16122>`__: DOC: Fix links for NEP 40 in NEP 41 +* `#16125 <https://github.com/numpy/numpy/pull/16125>`__: BUG: lib: Fix a problem with vectorize with default parameters. +* `#16129 <https://github.com/numpy/numpy/pull/16129>`__: ENH: Better error message when ``bins`` has float value in ``histogramdd``. +* `#16133 <https://github.com/numpy/numpy/pull/16133>`__: MAINT: Unify casting error creation (outside the iterator) +* `#16141 <https://github.com/numpy/numpy/pull/16141>`__: BENCH: Default to building HEAD instead of master +* `#16144 <https://github.com/numpy/numpy/pull/16144>`__: REL: Update master after NumPy 1.18.4 release +* `#16145 <https://github.com/numpy/numpy/pull/16145>`__: DOC: Add VSCode help link to importerror troubleshooting +* `#16147 <https://github.com/numpy/numpy/pull/16147>`__: CI: pin 32-bit manylinux2010 image tag +* `#16151 <https://github.com/numpy/numpy/pull/16151>`__: MAINT: Bump pytz from 2019.3 to 2020.1 +* `#16153 <https://github.com/numpy/numpy/pull/16153>`__: BUG: Correct loop order in MT19937 jump +* `#16155 <https://github.com/numpy/numpy/pull/16155>`__: CI: unpin 32-bit manylinux2010 image tag +* `#16162 <https://github.com/numpy/numpy/pull/16162>`__: BUG: add missing numpy/__init__.pxd to the wheel +* `#16168 <https://github.com/numpy/numpy/pull/16168>`__: BUG:Umath remove unnecessary include of simd.inc in fast_loop_macro.h +* `#16169 <https://github.com/numpy/numpy/pull/16169>`__: DOC,BLD: Add :doc: to whitelisted roles in refguide_check. +* `#16170 <https://github.com/numpy/numpy/pull/16170>`__: ENH: resync numpy/__init__.pxd with upstream +* `#16171 <https://github.com/numpy/numpy/pull/16171>`__: ENH: allow choosing which manylinux artifact to download +* `#16173 <https://github.com/numpy/numpy/pull/16173>`__: MAINT: Mark tests as a subpackage rather than data. +* `#16182 <https://github.com/numpy/numpy/pull/16182>`__: Update Docs : point users of np.outer to np.multiply.outer +* `#16183 <https://github.com/numpy/numpy/pull/16183>`__: DOC: Fix link to numpy docs in README. +* `#16185 <https://github.com/numpy/numpy/pull/16185>`__: ENH: Allow pickle with protocol 5 when higher is requested +* `#16188 <https://github.com/numpy/numpy/pull/16188>`__: MAINT: cleanups to _iotools.StringConverter +* `#16197 <https://github.com/numpy/numpy/pull/16197>`__: DOC: Unify cross-references between array joining methods +* `#16199 <https://github.com/numpy/numpy/pull/16199>`__: DOC: Improve docstring of ``numpy.core.records`` +* `#16201 <https://github.com/numpy/numpy/pull/16201>`__: DOC: update Code of Conduct committee +* `#16203 <https://github.com/numpy/numpy/pull/16203>`__: MAINT: Bump hypothesis from 5.10.4 to 5.12.0 +* `#16204 <https://github.com/numpy/numpy/pull/16204>`__: MAINT: Bump pytest from 5.4.1 to 5.4.2 +* `#16210 <https://github.com/numpy/numpy/pull/16210>`__: DOC: warn about runtime of shares_memory +* `#16213 <https://github.com/numpy/numpy/pull/16213>`__: ENH: backport scipy changes to openblas download script +* `#16214 <https://github.com/numpy/numpy/pull/16214>`__: BUG: skip complex256 arcsinh precision test on glibc2.17 +* `#16215 <https://github.com/numpy/numpy/pull/16215>`__: MAINT: Chain exceptions and use NameError in np.bmat +* `#16216 <https://github.com/numpy/numpy/pull/16216>`__: DOC,BLD: pin sphinx to <3.0 in doc_requirements.txt +* `#16223 <https://github.com/numpy/numpy/pull/16223>`__: BUG: fix signature of PyArray_SearchSorted in __init__.pxd +* `#16224 <https://github.com/numpy/numpy/pull/16224>`__: ENH: add manylinux1 openblas hashes +* `#16226 <https://github.com/numpy/numpy/pull/16226>`__: DOC: Fix Generator.choice docstring +* `#16227 <https://github.com/numpy/numpy/pull/16227>`__: DOC: Add PyDev instructions to troubleshooting doc +* `#16228 <https://github.com/numpy/numpy/pull/16228>`__: DOC: Add Clang and MSVC to supported compilers list +* `#16240 <https://github.com/numpy/numpy/pull/16240>`__: DOC: Warn about behavior of ptp with signed integers. +* `#16258 <https://github.com/numpy/numpy/pull/16258>`__: DOC: Update the f2py section of the "Using Python as Glue" page. +* `#16263 <https://github.com/numpy/numpy/pull/16263>`__: BUG: Add missing decref in fromarray error path +* `#16265 <https://github.com/numpy/numpy/pull/16265>`__: ENH: Add tool for downloading release wheels from Anaconda. +* `#16269 <https://github.com/numpy/numpy/pull/16269>`__: DOC: Fix typos and cosmetic issues +* `#16280 <https://github.com/numpy/numpy/pull/16280>`__: REL: Prepare for the 1.19.0 release +* `#16293 <https://github.com/numpy/numpy/pull/16293>`__: BUG: Fix tools/download-wheels.py. +* `#16301 <https://github.com/numpy/numpy/pull/16301>`__: BUG: Require Python >= 3.6 in setup.py +* `#16312 <https://github.com/numpy/numpy/pull/16312>`__: BUG: relpath fails for different drives on windows +* `#16314 <https://github.com/numpy/numpy/pull/16314>`__: DOC: Fix documentation rendering, +* `#16341 <https://github.com/numpy/numpy/pull/16341>`__: BUG: Don't segfault on bad __len__ when assigning. (gh-16327) +* `#16342 <https://github.com/numpy/numpy/pull/16342>`__: MAINT: Stop Using PyEval_Call* and simplify some uses +* `#16343 <https://github.com/numpy/numpy/pull/16343>`__: BLD: Avoid "visibility attribute not supported" warning. +* `#16344 <https://github.com/numpy/numpy/pull/16344>`__: BUG: Allow attaching documentation twice in add_docstring +* `#16355 <https://github.com/numpy/numpy/pull/16355>`__: MAINT: Remove f-strings in setup.py. (gh-16346) +* `#16356 <https://github.com/numpy/numpy/pull/16356>`__: BUG: Indentation for docstrings +* `#16358 <https://github.com/numpy/numpy/pull/16358>`__: BUG: Fix dtype leak in `PyArray_FromAny` error path +* `#16383 <https://github.com/numpy/numpy/pull/16383>`__: ENH: Optimize Cpu feature detect in X86, fix for GCC on macOS... +* `#16398 <https://github.com/numpy/numpy/pull/16398>`__: MAINT: core: Use a raw string for the fromstring docstring. +* `#16399 <https://github.com/numpy/numpy/pull/16399>`__: MAINT: Make ctypes optional on Windows +* `#16400 <https://github.com/numpy/numpy/pull/16400>`__: BUG: Fix small leaks in error path and ``empty_like`` with shape +* `#16402 <https://github.com/numpy/numpy/pull/16402>`__: TST, MAINT: Fix detecting and testing armhf features +* `#16412 <https://github.com/numpy/numpy/pull/16412>`__: DOC,BLD: Update sphinx conf to use xelatex. +* `#16413 <https://github.com/numpy/numpy/pull/16413>`__: DOC,BLD: Update make dist html target. +* `#16414 <https://github.com/numpy/numpy/pull/16414>`__: MAINT, DOC: add index for user docs. +* `#16437 <https://github.com/numpy/numpy/pull/16437>`__: MAINT: support python 3.10 +* `#16456 <https://github.com/numpy/numpy/pull/16456>`__: DOC: Fix troubleshooting code snippet when env vars are empty +* `#16457 <https://github.com/numpy/numpy/pull/16457>`__: REL: Prepare for the NumPy 1.19.0rc2 release. +* `#16526 <https://github.com/numpy/numpy/pull/16526>`__: MAINT:ARMHF Fix detecting feature groups NEON_HALF and NEON_VFPV4 +* `#16527 <https://github.com/numpy/numpy/pull/16527>`__: BUG:random: Error when ``size`` is smaller than broadcast input... +* `#16528 <https://github.com/numpy/numpy/pull/16528>`__: BUG: fix GCC 10 major version comparison +* `#16563 <https://github.com/numpy/numpy/pull/16563>`__: BUG: Ensure SeedSequence 0-padding does not collide with spawn... +* `#16586 <https://github.com/numpy/numpy/pull/16586>`__: BUG: fix sin/cos bug when input is strided array +* `#16602 <https://github.com/numpy/numpy/pull/16602>`__: MAINT: Move and improve ``test_ignore_nan_ulperror``. +* `#16645 <https://github.com/numpy/numpy/pull/16645>`__: REL: Update 1.19.0-changelog.rst for 1.19.0 release. diff --git a/doc/neps/_static/casting_flow.svg b/doc/neps/_static/casting_flow.svg new file mode 100644 index 000000000..8b4b96477 --- /dev/null +++ b/doc/neps/_static/casting_flow.svg @@ -0,0 +1,2212 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="908pt" + height="444pt" + viewBox="0 0 908 444" + version="1.1" + id="svg2577" + sodipodi:docname="casting_flow.svg" + inkscape:version="1.0rc1 (09960d6f05, 2020-04-09)"> + <metadata + id="metadata2581"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + inkscape:document-rotation="0" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2560" + inkscape:window-height="1376" + id="namedview2579" + showgrid="false" + inkscape:zoom="1.1348363" + inkscape:cx="754.6365" + inkscape:cy="382.73477" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="surface392452" /> + <defs + id="defs1910"> + <g + id="g1908"> + <symbol + overflow="visible" + id="glyph0-0"> + <path + style="stroke:none;" + d="M 0.640625 2.265625 L 0.640625 -9.015625 L 7.03125 -9.015625 L 7.03125 2.265625 Z M 1.359375 1.546875 L 6.328125 1.546875 L 6.328125 -8.296875 L 1.359375 -8.296875 Z M 1.359375 1.546875 " + id="path1734" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-1"> + <path + style="stroke:none;" + d="M 8.234375 -8.609375 L 8.234375 -7.28125 C 7.816406 -7.675781 7.363281 -7.96875 6.875 -8.15625 C 6.394531 -8.351562 5.882812 -8.453125 5.34375 -8.453125 C 4.28125 -8.453125 3.460938 -8.125 2.890625 -7.46875 C 2.328125 -6.820312 2.046875 -5.882812 2.046875 -4.65625 C 2.046875 -3.425781 2.328125 -2.484375 2.890625 -1.828125 C 3.460938 -1.179688 4.28125 -0.859375 5.34375 -0.859375 C 5.882812 -0.859375 6.394531 -0.953125 6.875 -1.140625 C 7.363281 -1.335938 7.816406 -1.632812 8.234375 -2.03125 L 8.234375 -0.71875 C 7.796875 -0.414062 7.328125 -0.1875 6.828125 -0.03125 C 6.335938 0.113281 5.820312 0.1875 5.28125 0.1875 C 3.863281 0.1875 2.75 -0.242188 1.9375 -1.109375 C 1.125 -1.972656 0.71875 -3.15625 0.71875 -4.65625 C 0.71875 -6.15625 1.125 -7.335938 1.9375 -8.203125 C 2.75 -9.066406 3.863281 -9.5 5.28125 -9.5 C 5.832031 -9.5 6.351562 -9.421875 6.84375 -9.265625 C 7.34375 -9.117188 7.804688 -8.898438 8.234375 -8.609375 Z M 8.234375 -8.609375 " + id="path1737" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-2"> + <path + style="stroke:none;" + d="M 4.390625 -3.515625 C 3.460938 -3.515625 2.816406 -3.40625 2.453125 -3.1875 C 2.097656 -2.976562 1.921875 -2.617188 1.921875 -2.109375 C 1.921875 -1.703125 2.050781 -1.378906 2.3125 -1.140625 C 2.582031 -0.898438 2.953125 -0.78125 3.421875 -0.78125 C 4.054688 -0.78125 4.566406 -1.003906 4.953125 -1.453125 C 5.335938 -1.910156 5.53125 -2.515625 5.53125 -3.265625 L 5.53125 -3.515625 Z M 6.671875 -4 L 6.671875 0 L 5.53125 0 L 5.53125 -1.0625 C 5.269531 -0.632812 4.941406 -0.316406 4.546875 -0.109375 C 4.160156 0.0859375 3.679688 0.1875 3.109375 0.1875 C 2.390625 0.1875 1.816406 -0.015625 1.390625 -0.421875 C 0.972656 -0.828125 0.765625 -1.363281 0.765625 -2.03125 C 0.765625 -2.820312 1.023438 -3.414062 1.546875 -3.8125 C 2.078125 -4.21875 2.867188 -4.421875 3.921875 -4.421875 L 5.53125 -4.421875 L 5.53125 -4.53125 C 5.53125 -5.0625 5.351562 -5.46875 5 -5.75 C 4.65625 -6.039062 4.171875 -6.1875 3.546875 -6.1875 C 3.140625 -6.1875 2.75 -6.140625 2.375 -6.046875 C 2 -5.953125 1.632812 -5.8125 1.28125 -5.625 L 1.28125 -6.671875 C 1.695312 -6.835938 2.101562 -6.960938 2.5 -7.046875 C 2.894531 -7.128906 3.28125 -7.171875 3.65625 -7.171875 C 4.675781 -7.171875 5.429688 -6.90625 5.921875 -6.375 C 6.421875 -5.851562 6.671875 -5.0625 6.671875 -4 Z M 6.671875 -4 " + id="path1740" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-3"> + <path + style="stroke:none;" + d="M 5.671875 -6.796875 L 5.671875 -5.703125 C 5.347656 -5.867188 5.007812 -5.992188 4.65625 -6.078125 C 4.300781 -6.160156 3.9375 -6.203125 3.5625 -6.203125 C 3 -6.203125 2.570312 -6.113281 2.28125 -5.9375 C 2 -5.769531 1.859375 -5.507812 1.859375 -5.15625 C 1.859375 -4.882812 1.957031 -4.671875 2.15625 -4.515625 C 2.363281 -4.367188 2.773438 -4.226562 3.390625 -4.09375 L 3.78125 -4 C 4.601562 -3.832031 5.1875 -3.585938 5.53125 -3.265625 C 5.875 -2.941406 6.046875 -2.5 6.046875 -1.9375 C 6.046875 -1.28125 5.785156 -0.757812 5.265625 -0.375 C 4.753906 0 4.050781 0.1875 3.15625 0.1875 C 2.78125 0.1875 2.390625 0.148438 1.984375 0.078125 C 1.578125 0.00390625 1.144531 -0.101562 0.6875 -0.25 L 0.6875 -1.4375 C 1.113281 -1.21875 1.53125 -1.050781 1.9375 -0.9375 C 2.351562 -0.832031 2.765625 -0.78125 3.171875 -0.78125 C 3.710938 -0.78125 4.128906 -0.875 4.421875 -1.0625 C 4.710938 -1.25 4.859375 -1.507812 4.859375 -1.84375 C 4.859375 -2.15625 4.753906 -2.394531 4.546875 -2.5625 C 4.335938 -2.726562 3.875 -2.890625 3.15625 -3.046875 L 2.765625 -3.140625 C 2.046875 -3.285156 1.53125 -3.515625 1.21875 -3.828125 C 0.90625 -4.140625 0.75 -4.566406 0.75 -5.109375 C 0.75 -5.765625 0.976562 -6.269531 1.4375 -6.625 C 1.90625 -6.988281 2.570312 -7.171875 3.4375 -7.171875 C 3.851562 -7.171875 4.25 -7.140625 4.625 -7.078125 C 5 -7.015625 5.347656 -6.921875 5.671875 -6.796875 Z M 5.671875 -6.796875 " + id="path1743" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-4"> + <path + style="stroke:none;" + d="M 2.34375 -8.984375 L 2.34375 -7 L 4.71875 -7 L 4.71875 -6.109375 L 2.34375 -6.109375 L 2.34375 -2.3125 C 2.34375 -1.738281 2.421875 -1.367188 2.578125 -1.203125 C 2.734375 -1.046875 3.050781 -0.96875 3.53125 -0.96875 L 4.71875 -0.96875 L 4.71875 0 L 3.53125 0 C 2.644531 0 2.03125 -0.164062 1.6875 -0.5 C 1.351562 -0.832031 1.1875 -1.4375 1.1875 -2.3125 L 1.1875 -6.109375 L 0.34375 -6.109375 L 0.34375 -7 L 1.1875 -7 L 1.1875 -8.984375 Z M 2.34375 -8.984375 " + id="path1746" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-5"> + <path + style="stroke:none;" + d="M 1.203125 -7 L 2.359375 -7 L 2.359375 0 L 1.203125 0 Z M 1.203125 -9.71875 L 2.359375 -9.71875 L 2.359375 -8.265625 L 1.203125 -8.265625 Z M 1.203125 -9.71875 " + id="path1749" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-6"> + <path + style="stroke:none;" + d="M 7.015625 -4.21875 L 7.015625 0 L 5.875 0 L 5.875 -4.1875 C 5.875 -4.851562 5.742188 -5.347656 5.484375 -5.671875 C 5.222656 -6.003906 4.835938 -6.171875 4.328125 -6.171875 C 3.703125 -6.171875 3.207031 -5.972656 2.84375 -5.578125 C 2.488281 -5.179688 2.3125 -4.640625 2.3125 -3.953125 L 2.3125 0 L 1.15625 0 L 1.15625 -7 L 2.3125 -7 L 2.3125 -5.90625 C 2.59375 -6.332031 2.914062 -6.648438 3.28125 -6.859375 C 3.65625 -7.066406 4.085938 -7.171875 4.578125 -7.171875 C 5.378906 -7.171875 5.984375 -6.921875 6.390625 -6.421875 C 6.804688 -5.921875 7.015625 -5.1875 7.015625 -4.21875 Z M 7.015625 -4.21875 " + id="path1752" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-7"> + <path + style="stroke:none;" + d="M 5.8125 -3.578125 C 5.8125 -4.410156 5.640625 -5.054688 5.296875 -5.515625 C 4.953125 -5.972656 4.46875 -6.203125 3.84375 -6.203125 C 3.226562 -6.203125 2.75 -5.972656 2.40625 -5.515625 C 2.0625 -5.054688 1.890625 -4.410156 1.890625 -3.578125 C 1.890625 -2.753906 2.0625 -2.113281 2.40625 -1.65625 C 2.75 -1.195312 3.226562 -0.96875 3.84375 -0.96875 C 4.46875 -0.96875 4.953125 -1.195312 5.296875 -1.65625 C 5.640625 -2.113281 5.8125 -2.753906 5.8125 -3.578125 Z M 6.953125 -0.875 C 6.953125 0.320312 6.6875 1.207031 6.15625 1.78125 C 5.632812 2.363281 4.828125 2.65625 3.734375 2.65625 C 3.328125 2.65625 2.945312 2.625 2.59375 2.5625 C 2.238281 2.507812 1.890625 2.421875 1.546875 2.296875 L 1.546875 1.171875 C 1.890625 1.359375 2.222656 1.492188 2.546875 1.578125 C 2.878906 1.671875 3.21875 1.71875 3.5625 1.71875 C 4.3125 1.71875 4.875 1.519531 5.25 1.125 C 5.625 0.726562 5.8125 0.132812 5.8125 -0.65625 L 5.8125 -1.234375 C 5.570312 -0.816406 5.265625 -0.503906 4.890625 -0.296875 C 4.523438 -0.0976562 4.082031 0 3.5625 0 C 2.707031 0 2.015625 -0.328125 1.484375 -0.984375 C 0.960938 -1.640625 0.703125 -2.503906 0.703125 -3.578125 C 0.703125 -4.660156 0.960938 -5.53125 1.484375 -6.1875 C 2.015625 -6.84375 2.707031 -7.171875 3.5625 -7.171875 C 4.082031 -7.171875 4.523438 -7.066406 4.890625 -6.859375 C 5.265625 -6.648438 5.570312 -6.34375 5.8125 -5.9375 L 5.8125 -7 L 6.953125 -7 Z M 6.953125 -0.875 " + id="path1755" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-8"> + <path + style="stroke:none;" + d="M 1.25 -9.328125 L 2.515625 -9.328125 L 2.515625 0 L 1.25 0 Z M 1.25 -9.328125 " + id="path1758" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-9"> + <path + style="stroke:none;" + d="M 6.65625 -5.65625 C 6.9375 -6.164062 7.273438 -6.546875 7.671875 -6.796875 C 8.078125 -7.046875 8.550781 -7.171875 9.09375 -7.171875 C 9.820312 -7.171875 10.382812 -6.914062 10.78125 -6.40625 C 11.175781 -5.894531 11.375 -5.164062 11.375 -4.21875 L 11.375 0 L 10.21875 0 L 10.21875 -4.1875 C 10.21875 -4.851562 10.097656 -5.347656 9.859375 -5.671875 C 9.628906 -6.003906 9.269531 -6.171875 8.78125 -6.171875 C 8.1875 -6.171875 7.710938 -5.972656 7.359375 -5.578125 C 7.015625 -5.179688 6.84375 -4.640625 6.84375 -3.953125 L 6.84375 0 L 5.6875 0 L 5.6875 -4.1875 C 5.6875 -4.863281 5.566406 -5.363281 5.328125 -5.6875 C 5.097656 -6.007812 4.734375 -6.171875 4.234375 -6.171875 C 3.648438 -6.171875 3.179688 -5.96875 2.828125 -5.5625 C 2.484375 -5.164062 2.3125 -4.628906 2.3125 -3.953125 L 2.3125 0 L 1.15625 0 L 1.15625 -7 L 2.3125 -7 L 2.3125 -5.90625 C 2.582031 -6.332031 2.898438 -6.648438 3.265625 -6.859375 C 3.628906 -7.066406 4.0625 -7.171875 4.5625 -7.171875 C 5.070312 -7.171875 5.503906 -7.039062 5.859375 -6.78125 C 6.222656 -6.519531 6.488281 -6.144531 6.65625 -5.65625 Z M 6.65625 -5.65625 " + id="path1761" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-10"> + <path + style="stroke:none;" + d="M 2.3125 -1.046875 L 2.3125 2.65625 L 1.15625 2.65625 L 1.15625 -7 L 2.3125 -7 L 2.3125 -5.9375 C 2.5625 -6.351562 2.867188 -6.660156 3.234375 -6.859375 C 3.597656 -7.066406 4.039062 -7.171875 4.5625 -7.171875 C 5.40625 -7.171875 6.09375 -6.832031 6.625 -6.15625 C 7.15625 -5.476562 7.421875 -4.59375 7.421875 -3.5 C 7.421875 -2.394531 7.15625 -1.503906 6.625 -0.828125 C 6.09375 -0.148438 5.40625 0.1875 4.5625 0.1875 C 4.039062 0.1875 3.597656 0.0859375 3.234375 -0.109375 C 2.867188 -0.316406 2.5625 -0.628906 2.3125 -1.046875 Z M 6.234375 -3.5 C 6.234375 -4.34375 6.054688 -5.003906 5.703125 -5.484375 C 5.359375 -5.960938 4.882812 -6.203125 4.28125 -6.203125 C 3.664062 -6.203125 3.179688 -5.960938 2.828125 -5.484375 C 2.484375 -5.003906 2.3125 -4.34375 2.3125 -3.5 C 2.3125 -2.644531 2.484375 -1.976562 2.828125 -1.5 C 3.179688 -1.019531 3.664062 -0.78125 4.28125 -0.78125 C 4.882812 -0.78125 5.359375 -1.019531 5.703125 -1.5 C 6.054688 -1.976562 6.234375 -2.644531 6.234375 -3.5 Z M 6.234375 -3.5 " + id="path1764" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-11"> + <path + style="stroke:none;" + d="M 1.203125 -9.71875 L 2.359375 -9.71875 L 2.359375 0 L 1.203125 0 Z M 1.203125 -9.71875 " + id="path1767" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-12"> + <path + style="stroke:none;" + d="M 1.09375 -9.71875 L 3.75 -9.71875 L 3.75 -8.828125 L 2.25 -8.828125 L 2.25 0.796875 L 3.75 0.796875 L 3.75 1.6875 L 1.09375 1.6875 Z M 1.09375 -9.71875 " + id="path1770" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-13"> + <path + style="stroke:none;" + d="M 2.453125 -1.0625 L 6.859375 -1.0625 L 6.859375 0 L 0.9375 0 L 0.9375 -1.0625 C 1.414062 -1.5625 2.066406 -2.226562 2.890625 -3.0625 C 3.722656 -3.894531 4.242188 -4.429688 4.453125 -4.671875 C 4.859375 -5.128906 5.140625 -5.515625 5.296875 -5.828125 C 5.460938 -6.140625 5.546875 -6.445312 5.546875 -6.75 C 5.546875 -7.25 5.367188 -7.65625 5.015625 -7.96875 C 4.671875 -8.28125 4.21875 -8.4375 3.65625 -8.4375 C 3.257812 -8.4375 2.84375 -8.363281 2.40625 -8.21875 C 1.96875 -8.082031 1.5 -7.878906 1 -7.609375 L 1 -8.875 C 1.507812 -9.082031 1.984375 -9.238281 2.421875 -9.34375 C 2.867188 -9.445312 3.273438 -9.5 3.640625 -9.5 C 4.609375 -9.5 5.378906 -9.253906 5.953125 -8.765625 C 6.523438 -8.285156 6.8125 -7.640625 6.8125 -6.828125 C 6.8125 -6.453125 6.738281 -6.09375 6.59375 -5.75 C 6.445312 -5.40625 6.1875 -5 5.8125 -4.53125 C 5.707031 -4.40625 5.375 -4.054688 4.8125 -3.484375 C 4.257812 -2.910156 3.472656 -2.101562 2.453125 -1.0625 Z M 2.453125 -1.0625 " + id="path1773" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-14"> + <path + style="stroke:none;" + d="M 4.84375 -8.234375 L 1.65625 -3.25 L 4.84375 -3.25 Z M 4.5 -9.328125 L 6.09375 -9.328125 L 6.09375 -3.25 L 7.421875 -3.25 L 7.421875 -2.203125 L 6.09375 -2.203125 L 6.09375 0 L 4.84375 0 L 4.84375 -2.203125 L 0.625 -2.203125 L 0.625 -3.421875 Z M 4.5 -9.328125 " + id="path1776" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-15"> + <path + style="stroke:none;" + d="M 1.5 -1.59375 L 2.8125 -1.59375 L 2.8125 -0.515625 L 1.796875 1.484375 L 0.984375 1.484375 L 1.5 -0.515625 Z M 1.5 -1.59375 " + id="path1779" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-16"> + <path + style="stroke:none;" + d="" + id="path1782" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-17"> + <path + style="stroke:none;" + d="M 6.84375 -9.015625 L 6.84375 -7.796875 C 6.363281 -8.023438 5.910156 -8.191406 5.484375 -8.296875 C 5.066406 -8.410156 4.660156 -8.46875 4.265625 -8.46875 C 3.578125 -8.46875 3.046875 -8.332031 2.671875 -8.0625 C 2.296875 -7.800781 2.109375 -7.425781 2.109375 -6.9375 C 2.109375 -6.519531 2.234375 -6.207031 2.484375 -6 C 2.734375 -5.789062 3.203125 -5.625 3.890625 -5.5 L 4.65625 -5.34375 C 5.59375 -5.15625 6.285156 -4.835938 6.734375 -4.390625 C 7.179688 -3.941406 7.40625 -3.335938 7.40625 -2.578125 C 7.40625 -1.671875 7.101562 -0.984375 6.5 -0.515625 C 5.894531 -0.046875 5.007812 0.1875 3.84375 0.1875 C 3.394531 0.1875 2.921875 0.132812 2.421875 0.03125 C 1.929688 -0.0703125 1.414062 -0.21875 0.875 -0.40625 L 0.875 -1.71875 C 1.394531 -1.425781 1.898438 -1.207031 2.390625 -1.0625 C 2.878906 -0.914062 3.363281 -0.84375 3.84375 -0.84375 C 4.5625 -0.84375 5.113281 -0.984375 5.5 -1.265625 C 5.894531 -1.546875 6.09375 -1.953125 6.09375 -2.484375 C 6.09375 -2.941406 5.953125 -3.296875 5.671875 -3.546875 C 5.390625 -3.804688 4.925781 -4.003906 4.28125 -4.140625 L 3.515625 -4.28125 C 2.578125 -4.46875 1.894531 -4.757812 1.46875 -5.15625 C 1.050781 -5.5625 0.84375 -6.117188 0.84375 -6.828125 C 0.84375 -7.660156 1.132812 -8.3125 1.71875 -8.78125 C 2.300781 -9.257812 3.101562 -9.5 4.125 -9.5 C 4.5625 -9.5 5.003906 -9.457031 5.453125 -9.375 C 5.910156 -9.300781 6.375 -9.179688 6.84375 -9.015625 Z M 6.84375 -9.015625 " + id="path1785" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-18"> + <path + style="stroke:none;" + d="M 5.265625 -5.921875 C 5.128906 -5.992188 4.984375 -6.046875 4.828125 -6.078125 C 4.679688 -6.117188 4.519531 -6.140625 4.34375 -6.140625 C 3.6875 -6.140625 3.179688 -5.925781 2.828125 -5.5 C 2.484375 -5.082031 2.3125 -4.476562 2.3125 -3.6875 L 2.3125 0 L 1.15625 0 L 1.15625 -7 L 2.3125 -7 L 2.3125 -5.90625 C 2.5625 -6.332031 2.878906 -6.648438 3.265625 -6.859375 C 3.648438 -7.066406 4.117188 -7.171875 4.671875 -7.171875 C 4.753906 -7.171875 4.84375 -7.164062 4.9375 -7.15625 C 5.03125 -7.144531 5.132812 -7.128906 5.25 -7.109375 Z M 5.265625 -5.921875 " + id="path1788" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-19"> + <path + style="stroke:none;" + d="M 3.890625 -9.71875 L 3.890625 1.6875 L 1.25 1.6875 L 1.25 0.796875 L 2.734375 0.796875 L 2.734375 -8.828125 L 1.25 -8.828125 L 1.25 -9.71875 Z M 3.890625 -9.71875 " + id="path1791" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-20"> + <path + style="stroke:none;" + d="M 4.0625 -8.5 C 3.414062 -8.5 2.925781 -8.175781 2.59375 -7.53125 C 2.269531 -6.894531 2.109375 -5.9375 2.109375 -4.65625 C 2.109375 -3.375 2.269531 -2.410156 2.59375 -1.765625 C 2.925781 -1.128906 3.414062 -0.8125 4.0625 -0.8125 C 4.71875 -0.8125 5.207031 -1.128906 5.53125 -1.765625 C 5.863281 -2.410156 6.03125 -3.375 6.03125 -4.65625 C 6.03125 -5.9375 5.863281 -6.894531 5.53125 -7.53125 C 5.207031 -8.175781 4.71875 -8.5 4.0625 -8.5 Z M 4.0625 -9.5 C 5.113281 -9.5 5.914062 -9.082031 6.46875 -8.25 C 7.019531 -7.425781 7.296875 -6.226562 7.296875 -4.65625 C 7.296875 -3.082031 7.019531 -1.878906 6.46875 -1.046875 C 5.914062 -0.222656 5.113281 0.1875 4.0625 0.1875 C 3.019531 0.1875 2.222656 -0.222656 1.671875 -1.046875 C 1.117188 -1.878906 0.84375 -3.082031 0.84375 -4.65625 C 0.84375 -6.226562 1.117188 -7.425781 1.671875 -8.25 C 2.222656 -9.082031 3.019531 -9.5 4.0625 -9.5 Z M 4.0625 -9.5 " + id="path1794" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-21"> + <path + style="stroke:none;" + d="M 5.8125 -5.9375 L 5.8125 -9.71875 L 6.953125 -9.71875 L 6.953125 0 L 5.8125 0 L 5.8125 -1.046875 C 5.570312 -0.628906 5.265625 -0.316406 4.890625 -0.109375 C 4.523438 0.0859375 4.082031 0.1875 3.5625 0.1875 C 2.71875 0.1875 2.03125 -0.148438 1.5 -0.828125 C 0.96875 -1.503906 0.703125 -2.394531 0.703125 -3.5 C 0.703125 -4.59375 0.96875 -5.476562 1.5 -6.15625 C 2.03125 -6.832031 2.71875 -7.171875 3.5625 -7.171875 C 4.082031 -7.171875 4.523438 -7.066406 4.890625 -6.859375 C 5.265625 -6.660156 5.570312 -6.351562 5.8125 -5.9375 Z M 1.890625 -3.5 C 1.890625 -2.644531 2.0625 -1.976562 2.40625 -1.5 C 2.757812 -1.019531 3.238281 -0.78125 3.84375 -0.78125 C 4.457031 -0.78125 4.9375 -1.019531 5.28125 -1.5 C 5.632812 -1.976562 5.8125 -2.644531 5.8125 -3.5 C 5.8125 -4.34375 5.632812 -5.003906 5.28125 -5.484375 C 4.9375 -5.960938 4.457031 -6.203125 3.84375 -6.203125 C 3.238281 -6.203125 2.757812 -5.960938 2.40625 -5.484375 C 2.0625 -5.003906 1.890625 -4.34375 1.890625 -3.5 Z M 1.890625 -3.5 " + id="path1797" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-22"> + <path + style="stroke:none;" + d="M 1.203125 -7 L 2.359375 -7 L 2.359375 0.125 C 2.359375 1.019531 2.1875 1.664062 1.84375 2.0625 C 1.507812 2.457031 0.960938 2.65625 0.203125 2.65625 L -0.234375 2.65625 L -0.234375 1.6875 L 0.078125 1.6875 C 0.515625 1.6875 0.8125 1.582031 0.96875 1.375 C 1.125 1.175781 1.203125 0.757812 1.203125 0.125 Z M 1.203125 -9.71875 L 2.359375 -9.71875 L 2.359375 -8.265625 L 1.203125 -8.265625 Z M 1.203125 -9.71875 " + id="path1800" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-23"> + <path + style="stroke:none;" + d="M 1.09375 -2.765625 L 1.09375 -7 L 2.234375 -7 L 2.234375 -2.8125 C 2.234375 -2.144531 2.363281 -1.644531 2.625 -1.3125 C 2.882812 -0.976562 3.269531 -0.8125 3.78125 -0.8125 C 4.40625 -0.8125 4.894531 -1.007812 5.25 -1.40625 C 5.613281 -1.800781 5.796875 -2.34375 5.796875 -3.03125 L 5.796875 -7 L 6.953125 -7 L 6.953125 0 L 5.796875 0 L 5.796875 -1.078125 C 5.515625 -0.648438 5.191406 -0.332031 4.828125 -0.125 C 4.460938 0.0820312 4.035156 0.1875 3.546875 0.1875 C 2.742188 0.1875 2.132812 -0.0625 1.71875 -0.5625 C 1.300781 -1.0625 1.09375 -1.796875 1.09375 -2.765625 Z M 3.984375 -7.171875 Z M 3.984375 -7.171875 " + id="path1803" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-24"> + <path + style="stroke:none;" + d="M 6.515625 2.125 L 6.515625 3.015625 L -0.125 3.015625 L -0.125 2.125 Z M 6.515625 2.125 " + id="path1806" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-25"> + <path + style="stroke:none;" + d="M 7.1875 -3.78125 L 7.1875 -3.21875 L 1.90625 -3.21875 C 1.957031 -2.425781 2.195312 -1.820312 2.625 -1.40625 C 3.050781 -1 3.644531 -0.796875 4.40625 -0.796875 C 4.84375 -0.796875 5.269531 -0.847656 5.6875 -0.953125 C 6.101562 -1.066406 6.515625 -1.226562 6.921875 -1.4375 L 6.921875 -0.359375 C 6.515625 -0.179688 6.09375 -0.046875 5.65625 0.046875 C 5.21875 0.140625 4.78125 0.1875 4.34375 0.1875 C 3.21875 0.1875 2.328125 -0.132812 1.671875 -0.78125 C 1.023438 -1.4375 0.703125 -2.320312 0.703125 -3.4375 C 0.703125 -4.582031 1.007812 -5.488281 1.625 -6.15625 C 2.25 -6.832031 3.085938 -7.171875 4.140625 -7.171875 C 5.078125 -7.171875 5.816406 -6.863281 6.359375 -6.25 C 6.910156 -5.644531 7.1875 -4.820312 7.1875 -3.78125 Z M 6.046875 -4.125 C 6.035156 -4.75 5.859375 -5.25 5.515625 -5.625 C 5.171875 -6 4.71875 -6.1875 4.15625 -6.1875 C 3.507812 -6.1875 2.992188 -6.003906 2.609375 -5.640625 C 2.222656 -5.285156 2 -4.78125 1.9375 -4.125 Z M 6.046875 -4.125 " + id="path1809" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-26"> + <path + style="stroke:none;" + d="M 6.25 -6.734375 L 6.25 -5.65625 C 5.914062 -5.832031 5.585938 -5.960938 5.265625 -6.046875 C 4.941406 -6.140625 4.613281 -6.1875 4.28125 -6.1875 C 3.53125 -6.1875 2.945312 -5.953125 2.53125 -5.484375 C 2.125 -5.015625 1.921875 -4.351562 1.921875 -3.5 C 1.921875 -2.644531 2.125 -1.976562 2.53125 -1.5 C 2.945312 -1.03125 3.53125 -0.796875 4.28125 -0.796875 C 4.613281 -0.796875 4.941406 -0.835938 5.265625 -0.921875 C 5.585938 -1.015625 5.914062 -1.148438 6.25 -1.328125 L 6.25 -0.265625 C 5.925781 -0.117188 5.59375 -0.0078125 5.25 0.0625 C 4.90625 0.144531 4.539062 0.1875 4.15625 0.1875 C 3.09375 0.1875 2.25 -0.144531 1.625 -0.8125 C 1.007812 -1.476562 0.703125 -2.375 0.703125 -3.5 C 0.703125 -4.632812 1.015625 -5.53125 1.640625 -6.1875 C 2.273438 -6.84375 3.132812 -7.171875 4.21875 -7.171875 C 4.570312 -7.171875 4.914062 -7.132812 5.25 -7.0625 C 5.59375 -6.988281 5.925781 -6.878906 6.25 -6.734375 Z M 6.25 -6.734375 " + id="path1812" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-27"> + <path + style="stroke:none;" + d="M 3.921875 -6.1875 C 3.304688 -6.1875 2.816406 -5.945312 2.453125 -5.46875 C 2.097656 -4.988281 1.921875 -4.332031 1.921875 -3.5 C 1.921875 -2.65625 2.097656 -1.992188 2.453125 -1.515625 C 2.804688 -1.035156 3.296875 -0.796875 3.921875 -0.796875 C 4.535156 -0.796875 5.019531 -1.035156 5.375 -1.515625 C 5.726562 -2.003906 5.90625 -2.664062 5.90625 -3.5 C 5.90625 -4.320312 5.726562 -4.972656 5.375 -5.453125 C 5.019531 -5.941406 4.535156 -6.1875 3.921875 -6.1875 Z M 3.921875 -7.171875 C 4.921875 -7.171875 5.703125 -6.84375 6.265625 -6.1875 C 6.835938 -5.539062 7.125 -4.644531 7.125 -3.5 C 7.125 -2.351562 6.835938 -1.453125 6.265625 -0.796875 C 5.703125 -0.140625 4.921875 0.1875 3.921875 0.1875 C 2.910156 0.1875 2.117188 -0.140625 1.546875 -0.796875 C 0.984375 -1.453125 0.703125 -2.351562 0.703125 -3.5 C 0.703125 -4.644531 0.984375 -5.539062 1.546875 -6.1875 C 2.117188 -6.84375 2.910156 -7.171875 3.921875 -7.171875 Z M 3.921875 -7.171875 " + id="path1815" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-28"> + <path + style="stroke:none;" + d="M 3.96875 -9.703125 C 3.40625 -8.742188 2.988281 -7.796875 2.71875 -6.859375 C 2.445312 -5.929688 2.3125 -4.984375 2.3125 -4.015625 C 2.3125 -3.054688 2.445312 -2.101562 2.71875 -1.15625 C 3 -0.21875 3.414062 0.726562 3.96875 1.6875 L 2.96875 1.6875 C 2.34375 0.707031 1.875 -0.253906 1.5625 -1.203125 C 1.25 -2.148438 1.09375 -3.085938 1.09375 -4.015625 C 1.09375 -4.941406 1.25 -5.875 1.5625 -6.8125 C 1.875 -7.757812 2.34375 -8.722656 2.96875 -9.703125 Z M 3.96875 -9.703125 " + id="path1818" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-29"> + <path + style="stroke:none;" + d="M 1.03125 -9.703125 L 2.03125 -9.703125 C 2.65625 -8.722656 3.117188 -7.757812 3.421875 -6.8125 C 3.734375 -5.875 3.890625 -4.941406 3.890625 -4.015625 C 3.890625 -3.085938 3.734375 -2.148438 3.421875 -1.203125 C 3.117188 -0.253906 2.65625 0.707031 2.03125 1.6875 L 1.03125 1.6875 C 1.582031 0.726562 1.992188 -0.21875 2.265625 -1.15625 C 2.535156 -2.101562 2.671875 -3.054688 2.671875 -4.015625 C 2.671875 -4.984375 2.535156 -5.929688 2.265625 -6.859375 C 1.992188 -7.796875 1.582031 -8.742188 1.03125 -9.703125 Z M 1.03125 -9.703125 " + id="path1821" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-30"> + <path + style="stroke:none;" + d="M 4.0625 -4.4375 C 3.46875 -4.4375 3 -4.273438 2.65625 -3.953125 C 2.3125 -3.628906 2.140625 -3.1875 2.140625 -2.625 C 2.140625 -2.0625 2.3125 -1.617188 2.65625 -1.296875 C 3 -0.972656 3.46875 -0.8125 4.0625 -0.8125 C 4.664062 -0.8125 5.140625 -0.972656 5.484375 -1.296875 C 5.828125 -1.617188 6 -2.0625 6 -2.625 C 6 -3.1875 5.828125 -3.628906 5.484375 -3.953125 C 5.140625 -4.273438 4.664062 -4.4375 4.0625 -4.4375 Z M 2.8125 -4.96875 C 2.269531 -5.101562 1.847656 -5.351562 1.546875 -5.71875 C 1.242188 -6.09375 1.09375 -6.546875 1.09375 -7.078125 C 1.09375 -7.828125 1.359375 -8.414062 1.890625 -8.84375 C 2.421875 -9.28125 3.144531 -9.5 4.0625 -9.5 C 5 -9.5 5.726562 -9.28125 6.25 -8.84375 C 6.78125 -8.414062 7.046875 -7.828125 7.046875 -7.078125 C 7.046875 -6.546875 6.894531 -6.09375 6.59375 -5.71875 C 6.289062 -5.351562 5.875 -5.101562 5.34375 -4.96875 C 5.945312 -4.820312 6.414062 -4.539062 6.75 -4.125 C 7.09375 -3.71875 7.265625 -3.21875 7.265625 -2.625 C 7.265625 -1.71875 6.988281 -1.019531 6.4375 -0.53125 C 5.882812 -0.0507812 5.09375 0.1875 4.0625 0.1875 C 3.039062 0.1875 2.253906 -0.0507812 1.703125 -0.53125 C 1.148438 -1.019531 0.875 -1.71875 0.875 -2.625 C 0.875 -3.21875 1.039062 -3.71875 1.375 -4.125 C 1.71875 -4.539062 2.195312 -4.820312 2.8125 -4.96875 Z M 2.34375 -6.953125 C 2.34375 -6.472656 2.492188 -6.097656 2.796875 -5.828125 C 3.097656 -5.554688 3.519531 -5.421875 4.0625 -5.421875 C 4.601562 -5.421875 5.023438 -5.554688 5.328125 -5.828125 C 5.640625 -6.097656 5.796875 -6.472656 5.796875 -6.953125 C 5.796875 -7.441406 5.640625 -7.820312 5.328125 -8.09375 C 5.023438 -8.363281 4.601562 -8.5 4.0625 -8.5 C 3.519531 -8.5 3.097656 -8.363281 2.796875 -8.09375 C 2.492188 -7.820312 2.34375 -7.441406 2.34375 -6.953125 Z M 2.34375 -6.953125 " + id="path1824" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-31"> + <path + style="stroke:none;" + d="M 1.9375 -1.59375 L 3.203125 -1.59375 L 3.203125 0 L 1.9375 0 Z M 1.9375 -9.328125 L 3.203125 -9.328125 L 3.203125 -5.234375 L 3.078125 -3 L 2.0625 -3 L 1.9375 -5.234375 Z M 1.9375 -9.328125 " + id="path1827" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-32"> + <path + style="stroke:none;" + d="M 1.359375 -5.8125 L 9.359375 -5.8125 L 9.359375 -4.765625 L 1.359375 -4.765625 Z M 1.359375 -3.265625 L 9.359375 -3.265625 L 9.359375 -2.203125 L 1.359375 -2.203125 Z M 1.359375 -3.265625 " + id="path1830" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-33"> + <path + style="stroke:none;" + d="M 4.75 -9.71875 L 4.75 -8.765625 L 3.65625 -8.765625 C 3.238281 -8.765625 2.945312 -8.679688 2.78125 -8.515625 C 2.625 -8.347656 2.546875 -8.046875 2.546875 -7.609375 L 2.546875 -7 L 4.4375 -7 L 4.4375 -6.109375 L 2.546875 -6.109375 L 2.546875 0 L 1.390625 0 L 1.390625 -6.109375 L 0.296875 -6.109375 L 0.296875 -7 L 1.390625 -7 L 1.390625 -7.484375 C 1.390625 -8.265625 1.570312 -8.832031 1.9375 -9.1875 C 2.300781 -9.539062 2.875 -9.71875 3.65625 -9.71875 Z M 4.75 -9.71875 " + id="path1833" /> + </symbol> + <symbol + overflow="visible" + id="glyph0-34"> + <path + style="stroke:none;" + d="M 2.296875 -9.328125 L 2.296875 -5.859375 L 1.234375 -5.859375 L 1.234375 -9.328125 Z M 4.65625 -9.328125 L 4.65625 -5.859375 L 3.59375 -5.859375 L 3.59375 -9.328125 Z M 4.65625 -9.328125 " + id="path1836" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-0"> + <path + style="stroke:none;" + d="M 0.984375 3.484375 L 0.984375 -13.921875 L 10.859375 -13.921875 L 10.859375 3.484375 Z M 2.09375 2.390625 L 9.765625 2.390625 L 9.765625 -12.8125 L 2.09375 -12.8125 Z M 2.09375 2.390625 " + id="path1839" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-1"> + <path + style="stroke:none;" + d="M 12.515625 -6.578125 L 12.515625 0 L 9.046875 0 L 9.046875 -5.03125 C 9.046875 -5.96875 9.023438 -6.613281 8.984375 -6.96875 C 8.941406 -7.320312 8.867188 -7.582031 8.765625 -7.75 C 8.628906 -7.96875 8.445312 -8.140625 8.21875 -8.265625 C 7.988281 -8.390625 7.722656 -8.453125 7.421875 -8.453125 C 6.703125 -8.453125 6.132812 -8.175781 5.71875 -7.625 C 5.3125 -7.070312 5.109375 -6.300781 5.109375 -5.3125 L 5.109375 0 L 1.65625 0 L 1.65625 -10.796875 L 5.109375 -10.796875 L 5.109375 -9.21875 C 5.628906 -9.851562 6.179688 -10.316406 6.765625 -10.609375 C 7.347656 -10.910156 7.992188 -11.0625 8.703125 -11.0625 C 9.953125 -11.0625 10.898438 -10.675781 11.546875 -9.90625 C 12.191406 -9.144531 12.515625 -8.035156 12.515625 -6.578125 Z M 12.515625 -6.578125 " + id="path1842" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-2"> + <path + style="stroke:none;" + d="M 5.109375 -1.5625 L 5.109375 4.109375 L 1.65625 4.109375 L 1.65625 -10.796875 L 5.109375 -10.796875 L 5.109375 -9.21875 C 5.585938 -9.851562 6.113281 -10.316406 6.6875 -10.609375 C 7.269531 -10.910156 7.9375 -11.0625 8.6875 -11.0625 C 10.019531 -11.0625 11.113281 -10.53125 11.96875 -9.46875 C 12.820312 -8.414062 13.25 -7.054688 13.25 -5.390625 C 13.25 -3.722656 12.820312 -2.359375 11.96875 -1.296875 C 11.113281 -0.242188 10.019531 0.28125 8.6875 0.28125 C 7.9375 0.28125 7.269531 0.128906 6.6875 -0.171875 C 6.113281 -0.472656 5.585938 -0.9375 5.109375 -1.5625 Z M 7.40625 -8.546875 C 6.664062 -8.546875 6.097656 -8.273438 5.703125 -7.734375 C 5.304688 -7.191406 5.109375 -6.410156 5.109375 -5.390625 C 5.109375 -4.367188 5.304688 -3.585938 5.703125 -3.046875 C 6.097656 -2.503906 6.664062 -2.234375 7.40625 -2.234375 C 8.144531 -2.234375 8.707031 -2.5 9.09375 -3.03125 C 9.488281 -3.570312 9.6875 -4.359375 9.6875 -5.390625 C 9.6875 -6.421875 9.488281 -7.203125 9.09375 -7.734375 C 8.707031 -8.273438 8.144531 -8.546875 7.40625 -8.546875 Z M 7.40625 -8.546875 " + id="path1845" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-3"> + <path + style="stroke:none;" + d="M 2.015625 -3.734375 L 5.484375 -3.734375 L 5.484375 0 L 2.015625 0 Z M 2.015625 -3.734375 " + id="path1848" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-4"> + <path + style="stroke:none;" + d="M 6.5 -4.859375 C 5.78125 -4.859375 5.238281 -4.734375 4.875 -4.484375 C 4.507812 -4.242188 4.328125 -3.882812 4.328125 -3.40625 C 4.328125 -2.976562 4.472656 -2.640625 4.765625 -2.390625 C 5.054688 -2.140625 5.460938 -2.015625 5.984375 -2.015625 C 6.640625 -2.015625 7.1875 -2.242188 7.625 -2.703125 C 8.070312 -3.171875 8.296875 -3.757812 8.296875 -4.46875 L 8.296875 -4.859375 Z M 11.78125 -6.15625 L 11.78125 0 L 8.296875 0 L 8.296875 -1.59375 C 7.828125 -0.945312 7.300781 -0.472656 6.71875 -0.171875 C 6.144531 0.128906 5.445312 0.28125 4.625 0.28125 C 3.5 0.28125 2.585938 -0.0390625 1.890625 -0.6875 C 1.191406 -1.34375 0.84375 -2.191406 0.84375 -3.234375 C 0.84375 -4.503906 1.28125 -5.4375 2.15625 -6.03125 C 3.03125 -6.625 4.398438 -6.921875 6.265625 -6.921875 L 8.296875 -6.921875 L 8.296875 -7.1875 C 8.296875 -7.726562 8.078125 -8.125 7.640625 -8.375 C 7.210938 -8.632812 6.539062 -8.765625 5.625 -8.765625 C 4.882812 -8.765625 4.195312 -8.691406 3.5625 -8.546875 C 2.925781 -8.398438 2.335938 -8.175781 1.796875 -7.875 L 1.796875 -10.515625 C 2.535156 -10.691406 3.273438 -10.828125 4.015625 -10.921875 C 4.765625 -11.015625 5.515625 -11.0625 6.265625 -11.0625 C 8.210938 -11.0625 9.617188 -10.675781 10.484375 -9.90625 C 11.347656 -9.132812 11.78125 -7.882812 11.78125 -6.15625 Z M 11.78125 -6.15625 " + id="path1851" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-5"> + <path + style="stroke:none;" + d="M 9.6875 -7.859375 C 9.382812 -8.003906 9.082031 -8.109375 8.78125 -8.171875 C 8.476562 -8.242188 8.175781 -8.28125 7.875 -8.28125 C 6.988281 -8.28125 6.304688 -7.992188 5.828125 -7.421875 C 5.347656 -6.847656 5.109375 -6.03125 5.109375 -4.96875 L 5.109375 0 L 1.65625 0 L 1.65625 -10.796875 L 5.109375 -10.796875 L 5.109375 -9.03125 C 5.554688 -9.738281 6.066406 -10.253906 6.640625 -10.578125 C 7.210938 -10.898438 7.898438 -11.0625 8.703125 -11.0625 C 8.816406 -11.0625 8.941406 -11.054688 9.078125 -11.046875 C 9.210938 -11.035156 9.410156 -11.015625 9.671875 -10.984375 Z M 9.6875 -7.859375 " + id="path1854" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-6"> + <path + style="stroke:none;" + d="M 0.234375 -10.796875 L 3.6875 -10.796875 L 6.59375 -3.46875 L 9.0625 -10.796875 L 12.515625 -10.796875 L 7.96875 1.015625 C 7.519531 2.222656 6.988281 3.066406 6.375 3.546875 C 5.769531 4.023438 4.96875 4.265625 3.96875 4.265625 L 1.984375 4.265625 L 1.984375 2 L 3.0625 2 C 3.644531 2 4.066406 1.90625 4.328125 1.71875 C 4.597656 1.53125 4.804688 1.195312 4.953125 0.71875 L 5.046875 0.421875 Z M 0.234375 -10.796875 " + id="path1857" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-7"> + <path + style="stroke:none;" + d="M 7.4375 2.609375 L 4.578125 2.609375 C 3.597656 1.015625 2.875 -0.492188 2.40625 -1.921875 C 1.9375 -3.347656 1.703125 -4.769531 1.703125 -6.1875 C 1.703125 -7.59375 1.9375 -9.015625 2.40625 -10.453125 C 2.875 -11.898438 3.597656 -13.410156 4.578125 -14.984375 L 7.4375 -14.984375 C 6.613281 -13.460938 5.992188 -11.972656 5.578125 -10.515625 C 5.171875 -9.054688 4.96875 -7.617188 4.96875 -6.203125 C 4.96875 -4.773438 5.171875 -3.332031 5.578125 -1.875 C 5.992188 -0.414062 6.613281 1.078125 7.4375 2.609375 Z M 7.4375 2.609375 " + id="path1860" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-8"> + <path + style="stroke:none;" + d="M 7.265625 -11.34375 L 3.203125 -5.3125 L 7.265625 -5.3125 Z M 6.65625 -14.390625 L 10.78125 -14.390625 L 10.78125 -5.3125 L 12.828125 -5.3125 L 12.828125 -2.625 L 10.78125 -2.625 L 10.78125 0 L 7.265625 0 L 7.265625 -2.625 L 0.890625 -2.625 L 0.890625 -5.8125 Z M 6.65625 -14.390625 " + id="path1863" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-9"> + <path + style="stroke:none;" + d="M 5.6875 -2.734375 L 12.03125 -2.734375 L 12.03125 0 L 1.5625 0 L 1.5625 -2.734375 L 6.8125 -7.375 C 7.28125 -7.789062 7.628906 -8.203125 7.859375 -8.609375 C 8.085938 -9.015625 8.203125 -9.4375 8.203125 -9.875 C 8.203125 -10.550781 7.972656 -11.09375 7.515625 -11.5 C 7.066406 -11.914062 6.460938 -12.125 5.703125 -12.125 C 5.128906 -12.125 4.5 -12 3.8125 -11.75 C 3.125 -11.5 2.382812 -11.128906 1.59375 -10.640625 L 1.59375 -13.8125 C 2.4375 -14.082031 3.265625 -14.289062 4.078125 -14.4375 C 4.890625 -14.582031 5.691406 -14.65625 6.484375 -14.65625 C 8.203125 -14.65625 9.535156 -14.273438 10.484375 -13.515625 C 11.441406 -12.753906 11.921875 -11.695312 11.921875 -10.34375 C 11.921875 -9.5625 11.71875 -8.832031 11.3125 -8.15625 C 10.914062 -7.476562 10.066406 -6.566406 8.765625 -5.421875 Z M 5.6875 -2.734375 " + id="path1866" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-10"> + <path + style="stroke:none;" + d="M 2.015625 -3.734375 L 5.484375 -3.734375 L 5.484375 -0.796875 L 3.109375 2.8125 L 1.046875 2.8125 L 2.015625 -0.796875 Z M 2.015625 -3.734375 " + id="path1869" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-11"> + <path + style="stroke:none;" + d="" + id="path1872" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-12"> + <path + style="stroke:none;" + d="M 9 -9.21875 L 9 -15 L 12.484375 -15 L 12.484375 0 L 9 0 L 9 -1.5625 C 8.53125 -0.925781 8.007812 -0.457031 7.4375 -0.15625 C 6.863281 0.132812 6.203125 0.28125 5.453125 0.28125 C 4.117188 0.28125 3.023438 -0.242188 2.171875 -1.296875 C 1.316406 -2.359375 0.890625 -3.722656 0.890625 -5.390625 C 0.890625 -7.054688 1.316406 -8.414062 2.171875 -9.46875 C 3.023438 -10.53125 4.117188 -11.0625 5.453125 -11.0625 C 6.191406 -11.0625 6.847656 -10.910156 7.421875 -10.609375 C 8.003906 -10.316406 8.53125 -9.851562 9 -9.21875 Z M 6.734375 -2.234375 C 7.472656 -2.234375 8.035156 -2.5 8.421875 -3.03125 C 8.804688 -3.570312 9 -4.359375 9 -5.390625 C 9 -6.421875 8.804688 -7.203125 8.421875 -7.734375 C 8.035156 -8.273438 7.472656 -8.546875 6.734375 -8.546875 C 5.992188 -8.546875 5.429688 -8.273438 5.046875 -7.734375 C 4.660156 -7.203125 4.46875 -6.421875 4.46875 -5.390625 C 4.46875 -4.359375 4.660156 -3.570312 5.046875 -3.03125 C 5.429688 -2.5 5.992188 -2.234375 6.734375 -2.234375 Z M 6.734375 -2.234375 " + id="path1875" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-13"> + <path + style="stroke:none;" + d="M 5.421875 -13.875 L 5.421875 -10.796875 L 8.984375 -10.796875 L 8.984375 -8.328125 L 5.421875 -8.328125 L 5.421875 -3.75 C 5.421875 -3.25 5.519531 -2.910156 5.71875 -2.734375 C 5.925781 -2.554688 6.328125 -2.46875 6.921875 -2.46875 L 8.6875 -2.46875 L 8.6875 0 L 5.734375 0 C 4.367188 0 3.398438 -0.28125 2.828125 -0.84375 C 2.265625 -1.414062 1.984375 -2.382812 1.984375 -3.75 L 1.984375 -8.328125 L 0.265625 -8.328125 L 0.265625 -10.796875 L 1.984375 -10.796875 L 1.984375 -13.875 Z M 5.421875 -13.875 " + id="path1878" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-14"> + <path + style="stroke:none;" + d="M 12.4375 -5.421875 L 12.4375 -4.453125 L 4.375 -4.453125 C 4.457031 -3.640625 4.75 -3.03125 5.25 -2.625 C 5.75 -2.21875 6.445312 -2.015625 7.34375 -2.015625 C 8.070312 -2.015625 8.816406 -2.117188 9.578125 -2.328125 C 10.335938 -2.546875 11.117188 -2.875 11.921875 -3.3125 L 11.921875 -0.65625 C 11.109375 -0.34375 10.289062 -0.109375 9.46875 0.046875 C 8.65625 0.203125 7.84375 0.28125 7.03125 0.28125 C 5.070312 0.28125 3.550781 -0.210938 2.46875 -1.203125 C 1.382812 -2.203125 0.84375 -3.597656 0.84375 -5.390625 C 0.84375 -7.148438 1.375 -8.535156 2.4375 -9.546875 C 3.507812 -10.554688 4.976562 -11.0625 6.84375 -11.0625 C 8.539062 -11.0625 9.894531 -10.550781 10.90625 -9.53125 C 11.925781 -8.507812 12.4375 -7.140625 12.4375 -5.421875 Z M 8.890625 -6.578125 C 8.890625 -7.234375 8.695312 -7.757812 8.3125 -8.15625 C 7.9375 -8.5625 7.4375 -8.765625 6.8125 -8.765625 C 6.144531 -8.765625 5.601562 -8.578125 5.1875 -8.203125 C 4.769531 -7.828125 4.507812 -7.285156 4.40625 -6.578125 Z M 8.890625 -6.578125 " + id="path1881" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-15"> + <path + style="stroke:none;" + d="M 2.09375 -9.515625 L 14.453125 -9.515625 L 14.453125 -7.25 L 2.09375 -7.25 Z M 2.09375 -5.125 L 14.453125 -5.125 L 14.453125 -2.84375 L 2.09375 -2.84375 Z M 2.09375 -5.125 " + id="path1884" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-16"> + <path + style="stroke:none;" + d="M 1.65625 -10.796875 L 5.109375 -10.796875 L 5.109375 0 L 1.65625 0 Z M 1.65625 -15 L 5.109375 -15 L 5.109375 -12.1875 L 1.65625 -12.1875 Z M 1.65625 -15 " + id="path1887" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-17"> + <path + style="stroke:none;" + d="M 1.578125 2.609375 C 2.398438 1.078125 3.015625 -0.414062 3.421875 -1.875 C 3.835938 -3.332031 4.046875 -4.773438 4.046875 -6.203125 C 4.046875 -7.617188 3.835938 -9.054688 3.421875 -10.515625 C 3.015625 -11.972656 2.398438 -13.460938 1.578125 -14.984375 L 4.453125 -14.984375 C 5.421875 -13.410156 6.140625 -11.898438 6.609375 -10.453125 C 7.085938 -9.015625 7.328125 -7.59375 7.328125 -6.1875 C 7.328125 -4.769531 7.09375 -3.347656 6.625 -1.921875 C 6.15625 -0.492188 5.429688 1.015625 4.453125 2.609375 Z M 1.578125 2.609375 " + id="path1890" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-18"> + <path + style="stroke:none;" + d="M 10.09375 -10.46875 L 10.09375 -7.84375 C 9.351562 -8.144531 8.640625 -8.375 7.953125 -8.53125 C 7.265625 -8.6875 6.617188 -8.765625 6.015625 -8.765625 C 5.359375 -8.765625 4.867188 -8.679688 4.546875 -8.515625 C 4.222656 -8.359375 4.0625 -8.109375 4.0625 -7.765625 C 4.0625 -7.484375 4.179688 -7.269531 4.421875 -7.125 C 4.671875 -6.976562 5.109375 -6.867188 5.734375 -6.796875 L 6.328125 -6.71875 C 8.097656 -6.488281 9.285156 -6.113281 9.890625 -5.59375 C 10.503906 -5.082031 10.8125 -4.28125 10.8125 -3.1875 C 10.8125 -2.03125 10.390625 -1.160156 9.546875 -0.578125 C 8.703125 -0.00390625 7.4375 0.28125 5.75 0.28125 C 5.03125 0.28125 4.289062 0.222656 3.53125 0.109375 C 2.769531 -0.00390625 1.988281 -0.171875 1.1875 -0.390625 L 1.1875 -3.015625 C 1.875 -2.679688 2.578125 -2.429688 3.296875 -2.265625 C 4.023438 -2.097656 4.757812 -2.015625 5.5 -2.015625 C 6.175781 -2.015625 6.6875 -2.109375 7.03125 -2.296875 C 7.375 -2.484375 7.546875 -2.757812 7.546875 -3.125 C 7.546875 -3.4375 7.425781 -3.664062 7.1875 -3.8125 C 6.957031 -3.96875 6.488281 -4.085938 5.78125 -4.171875 L 5.171875 -4.25 C 3.640625 -4.4375 2.5625 -4.789062 1.9375 -5.3125 C 1.320312 -5.832031 1.015625 -6.625 1.015625 -7.6875 C 1.015625 -8.832031 1.40625 -9.679688 2.1875 -10.234375 C 2.976562 -10.785156 4.1875 -11.0625 5.8125 -11.0625 C 6.445312 -11.0625 7.113281 -11.007812 7.8125 -10.90625 C 8.507812 -10.8125 9.269531 -10.664062 10.09375 -10.46875 Z M 10.09375 -10.46875 " + id="path1893" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-19"> + <path + style="stroke:none;" + d="M 8.40625 -14.390625 L 8.40625 -9.046875 L 6.125 -9.046875 L 6.125 -14.390625 Z M 4.171875 -14.390625 L 4.171875 -9.046875 L 1.875 -9.046875 L 1.875 -14.390625 Z M 4.171875 -14.390625 " + id="path1896" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-20"> + <path + style="stroke:none;" + d="M 11.828125 -13.9375 L 11.828125 -10.890625 C 11.035156 -11.242188 10.265625 -11.507812 9.515625 -11.6875 C 8.765625 -11.875 8.054688 -11.96875 7.390625 -11.96875 C 6.503906 -11.96875 5.847656 -11.84375 5.421875 -11.59375 C 4.992188 -11.351562 4.78125 -10.976562 4.78125 -10.46875 C 4.78125 -10.082031 4.921875 -9.78125 5.203125 -9.5625 C 5.492188 -9.34375 6.015625 -9.15625 6.765625 -9 L 8.34375 -8.6875 C 9.945312 -8.363281 11.085938 -7.875 11.765625 -7.21875 C 12.441406 -6.5625 12.78125 -5.628906 12.78125 -4.421875 C 12.78125 -2.835938 12.304688 -1.65625 11.359375 -0.875 C 10.421875 -0.101562 8.984375 0.28125 7.046875 0.28125 C 6.140625 0.28125 5.222656 0.191406 4.296875 0.015625 C 3.378906 -0.148438 2.460938 -0.40625 1.546875 -0.75 L 1.546875 -3.890625 C 2.460938 -3.398438 3.347656 -3.03125 4.203125 -2.78125 C 5.066406 -2.53125 5.894531 -2.40625 6.6875 -2.40625 C 7.5 -2.40625 8.117188 -2.539062 8.546875 -2.8125 C 8.984375 -3.082031 9.203125 -3.46875 9.203125 -3.96875 C 9.203125 -4.414062 9.054688 -4.757812 8.765625 -5 C 8.472656 -5.25 7.890625 -5.472656 7.015625 -5.671875 L 5.578125 -5.984375 C 4.128906 -6.296875 3.070312 -6.789062 2.40625 -7.46875 C 1.75 -8.144531 1.421875 -9.050781 1.421875 -10.1875 C 1.421875 -11.625 1.878906 -12.726562 2.796875 -13.5 C 3.722656 -14.269531 5.054688 -14.65625 6.796875 -14.65625 C 7.585938 -14.65625 8.398438 -14.59375 9.234375 -14.46875 C 10.078125 -14.351562 10.941406 -14.175781 11.828125 -13.9375 Z M 11.828125 -13.9375 " + id="path1899" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-21"> + <path + style="stroke:none;" + d="M 9.078125 -7.21875 C 9.078125 -9.007812 8.910156 -10.273438 8.578125 -11.015625 C 8.242188 -11.753906 7.675781 -12.125 6.875 -12.125 C 6.082031 -12.125 5.515625 -11.753906 5.171875 -11.015625 C 4.828125 -10.273438 4.65625 -9.007812 4.65625 -7.21875 C 4.65625 -5.394531 4.828125 -4.109375 5.171875 -3.359375 C 5.515625 -2.617188 6.082031 -2.25 6.875 -2.25 C 7.664062 -2.25 8.226562 -2.617188 8.5625 -3.359375 C 8.90625 -4.109375 9.078125 -5.394531 9.078125 -7.21875 Z M 12.796875 -7.1875 C 12.796875 -4.800781 12.28125 -2.957031 11.25 -1.65625 C 10.226562 -0.363281 8.769531 0.28125 6.875 0.28125 C 4.976562 0.28125 3.515625 -0.363281 2.484375 -1.65625 C 1.453125 -2.957031 0.9375 -4.800781 0.9375 -7.1875 C 0.9375 -9.570312 1.453125 -11.410156 2.484375 -12.703125 C 3.515625 -14.003906 4.976562 -14.65625 6.875 -14.65625 C 8.769531 -14.65625 10.226562 -14.003906 11.25 -12.703125 C 12.28125 -11.410156 12.796875 -9.570312 12.796875 -7.1875 Z M 12.796875 -7.1875 " + id="path1902" /> + </symbol> + <symbol + overflow="visible" + id="glyph1-22"> + <path + style="stroke:none;" + d="M 15.578125 -6.859375 L 15.578125 -5.515625 L 11.796875 -1.71875 L 10.390625 -3.125 L 12.3125 -5.03125 L 1.125 -5.03125 L 1.125 -7.34375 L 12.3125 -7.34375 L 10.390625 -9.265625 L 11.796875 -10.65625 Z M 15.578125 -6.859375 " + id="path1905" /> + </symbol> + </g> + </defs> + <g + id="surface392452"> + <rect + x="0" + y="0" + width="908" + height="444" + style="fill:rgb(100%,100%,100%);fill-opacity:1;stroke:none;" + id="rect1912" /> + <path + style="fill-rule:evenodd;fill:rgb(41.176471%,77.254903%,47.058824%);fill-opacity:0.592157;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 37.690003 18.712741 L 47.059925 18.712741 L 47.059925 20.612741 L 37.690003 20.612741 Z M 37.690003 18.712741 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1914" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g1968"> + <use + xlink:href="#glyph0-1" + x="273.335938" + y="24.88878" + id="use1916" /> + <use + xlink:href="#glyph0-2" + x="282.224826" + y="24.88878" + id="use1918" /> + <use + xlink:href="#glyph0-3" + x="290.002604" + y="24.88878" + id="use1920" /> + <use + xlink:href="#glyph0-4" + x="296.669271" + y="24.88878" + id="use1922" /> + <use + xlink:href="#glyph0-5" + x="301.669271" + y="24.88878" + id="use1924" /> + <use + xlink:href="#glyph0-6" + x="305.280382" + y="24.88878" + id="use1926" /> + <use + xlink:href="#glyph0-7" + x="313.335938" + y="24.88878" + id="use1928" /> + <use + xlink:href="#glyph0-8" + x="321.391493" + y="24.88878" + id="use1930" /> + <use + xlink:href="#glyph0-9" + x="325.280382" + y="24.88878" + id="use1932" /> + <use + xlink:href="#glyph0-10" + x="337.780382" + y="24.88878" + id="use1934" /> + <use + xlink:href="#glyph0-11" + x="345.835938" + y="24.88878" + id="use1936" /> + <use + xlink:href="#glyph0-12" + x="349.447049" + y="24.88878" + id="use1938" /> + <use + xlink:href="#glyph0-8" + x="354.447049" + y="24.88878" + id="use1940" /> + <use + xlink:href="#glyph0-6" + x="358.335938" + y="24.88878" + id="use1942" /> + <use + xlink:href="#glyph0-4" + x="366.391493" + y="24.88878" + id="use1944" /> + <use + xlink:href="#glyph0-13" + x="371.391493" + y="24.88878" + id="use1946" /> + <use + xlink:href="#glyph0-14" + x="379.447049" + y="24.88878" + id="use1948" /> + <use + xlink:href="#glyph0-15" + x="387.502604" + y="24.88878" + id="use1950" /> + <use + xlink:href="#glyph0-16" + x="391.669271" + y="24.88878" + id="use1952" /> + <use + xlink:href="#glyph0-17" + x="395.835938" + y="24.88878" + id="use1954" /> + <use + xlink:href="#glyph0-4" + x="403.891493" + y="24.88878" + id="use1956" /> + <use + xlink:href="#glyph0-18" + x="408.891493" + y="24.88878" + id="use1958" /> + <use + xlink:href="#glyph0-5" + x="414.169271" + y="24.88878" + id="use1960" /> + <use + xlink:href="#glyph0-6" + x="417.780382" + y="24.88878" + id="use1962" /> + <use + xlink:href="#glyph0-7" + x="425.835938" + y="24.88878" + id="use1964" /> + <use + xlink:href="#glyph0-19" + x="433.891493" + y="24.88878" + id="use1966" /> + </g> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.2,0.2;stroke-miterlimit:10;" + d="M 42.374964 20.662937 L 42.374964 21.218991 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1970" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 42.124964 21.218991 L 42.374964 21.718991 L 42.624964 21.218991 Z M 42.124964 21.218991 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1972" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 32.07594 22.801609 L 36.211292 22.813327 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1974" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 36.586292 22.814499 L 36.085706 23.063132 L 36.211292 22.813327 L 36.087073 22.563132 Z M 36.586292 22.814499 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1976" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 56.489612 22.817429 L 48.53844 22.825046 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1978" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 48.16344 22.825437 L 48.66305 22.574851 L 48.53844 22.825046 L 48.663636 23.074851 Z M 48.16344 22.825437 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1980" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 41.592932 23.830124 L 40.322034 25.453757 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1982" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 40.090979 25.748874 L 40.202307 25.201218 L 40.322034 25.453757 L 40.596057 25.509421 Z M 40.090979 25.748874 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1984" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 43.056604 23.830124 L 44.151331 25.434812 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1986" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 44.362659 25.744577 L 43.874378 25.472507 L 44.151331 25.434812 L 44.287464 25.190671 Z M 44.362659 25.744577 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1988" /> + <path + style="fill-rule:evenodd;fill:rgb(61.176473%,63.921571%,97.647059%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 29.543714 22.614499 L 32.026331 21.881296 L 32.026331 23.714499 L 29.543714 23.714499 Z M 29.543714 22.614499 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path1990" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2002"> + <use + xlink:href="#glyph0-5" + x="107.921875" + y="94.923937" + id="use1992" /> + <use + xlink:href="#glyph0-6" + x="111.532986" + y="94.923937" + id="use1994" /> + <use + xlink:href="#glyph0-4" + x="119.588542" + y="94.923937" + id="use1996" /> + <use + xlink:href="#glyph0-13" + x="124.588542" + y="94.923937" + id="use1998" /> + <use + xlink:href="#glyph0-14" + x="132.644097" + y="94.923937" + id="use2000" /> + </g> + <path + style="fill-rule:evenodd;fill:rgb(100%,70.19608%,14.509805%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 56.539612 22.616257 L 58.539612 21.816257 L 58.539612 23.816257 L 56.539612 23.816257 Z M 56.539612 22.616257 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2004" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2012"> + <use + xlink:href="#glyph0-17" + x="647.332031" + y="95.959093" + id="use2006" /> + <use + xlink:href="#glyph0-13" + x="655.387587" + y="95.959093" + id="use2008" /> + <use + xlink:href="#glyph0-20" + x="663.443142" + y="95.959093" + id="use2010" /> + </g> + <path + style="fill-rule:evenodd;fill:rgb(41.176471%,77.254903%,47.058824%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 36.747425 21.881101 L 48.002503 21.881101 L 48.002503 23.781101 L 36.747425 23.781101 Z M 36.747425 21.881101 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2014" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2076"> + <use + xlink:href="#glyph0-2" + x="260.152344" + y="88.252062" + id="use2016" /> + <use + xlink:href="#glyph0-21" + x="267.930122" + y="88.252062" + id="use2018" /> + <use + xlink:href="#glyph0-22" + x="275.985677" + y="88.252062" + id="use2020" /> + <use + xlink:href="#glyph0-23" + x="279.596788" + y="88.252062" + id="use2022" /> + <use + xlink:href="#glyph0-3" + x="287.652344" + y="88.252062" + id="use2024" /> + <use + xlink:href="#glyph0-4" + x="294.31901" + y="88.252062" + id="use2026" /> + <use + xlink:href="#glyph0-24" + x="299.31901" + y="88.252062" + id="use2028" /> + <use + xlink:href="#glyph0-21" + x="305.707899" + y="88.252062" + id="use2030" /> + <use + xlink:href="#glyph0-25" + x="313.763455" + y="88.252062" + id="use2032" /> + <use + xlink:href="#glyph0-3" + x="321.541233" + y="88.252062" + id="use2034" /> + <use + xlink:href="#glyph0-26" + x="328.207899" + y="88.252062" + id="use2036" /> + <use + xlink:href="#glyph0-18" + x="335.152344" + y="88.252062" + id="use2038" /> + <use + xlink:href="#glyph0-5" + x="340.430122" + y="88.252062" + id="use2040" /> + <use + xlink:href="#glyph0-10" + x="344.041233" + y="88.252062" + id="use2042" /> + <use + xlink:href="#glyph0-4" + x="352.096788" + y="88.252062" + id="use2044" /> + <use + xlink:href="#glyph0-27" + x="357.096788" + y="88.252062" + id="use2046" /> + <use + xlink:href="#glyph0-18" + x="364.874566" + y="88.252062" + id="use2048" /> + <use + xlink:href="#glyph0-3" + x="370.152344" + y="88.252062" + id="use2050" /> + <use + xlink:href="#glyph0-28" + x="376.81901" + y="88.252062" + id="use2052" /> + <use + xlink:href="#glyph0-5" + x="381.81901" + y="88.252062" + id="use2054" /> + <use + xlink:href="#glyph0-6" + x="385.430122" + y="88.252062" + id="use2056" /> + <use + xlink:href="#glyph0-4" + x="393.485677" + y="88.252062" + id="use2058" /> + <use + xlink:href="#glyph0-13" + x="398.485677" + y="88.252062" + id="use2060" /> + <use + xlink:href="#glyph0-14" + x="406.541233" + y="88.252062" + id="use2062" /> + <use + xlink:href="#glyph0-15" + x="414.596788" + y="88.252062" + id="use2064" /> + <use + xlink:href="#glyph0-16" + x="418.763455" + y="88.252062" + id="use2066" /> + <use + xlink:href="#glyph0-17" + x="422.930122" + y="88.252062" + id="use2068" /> + <use + xlink:href="#glyph0-13" + x="430.985677" + y="88.252062" + id="use2070" /> + <use + xlink:href="#glyph0-20" + x="439.041233" + y="88.252062" + id="use2072" /> + <use + xlink:href="#glyph0-29" + x="447.096788" + y="88.252062" + id="use2074" /> + </g> + <path + style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 37.777112 25.887156 L 41.350745 25.887156 L 40.622815 27.887156 L 37.049182 27.887156 Z M 37.777112 25.887156 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2078" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2090"> + <use + xlink:href="#glyph0-5" + x="276.222656" + y="169.377062" + id="use2080" /> + <use + xlink:href="#glyph0-6" + x="279.833767" + y="169.377062" + id="use2082" /> + <use + xlink:href="#glyph0-4" + x="287.889323" + y="169.377062" + id="use2084" /> + <use + xlink:href="#glyph0-13" + x="292.889323" + y="169.377062" + id="use2086" /> + <use + xlink:href="#glyph0-14" + x="300.944878" + y="169.377062" + id="use2088" /> + </g> + <path + style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 44.26344 25.887156 L 46.748792 25.887156 L 46.020862 27.887156 L 43.535511 27.887156 Z M 44.26344 25.887156 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2092" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2098"> + <use + xlink:href="#glyph0-17" + x="403.40625" + y="169.377062" + id="use2094" /> + <use + xlink:href="#glyph0-30" + x="411.461806" + y="169.377062" + id="use2096" /> + </g> + <path + style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 57.539612 25.68364 L 59.946643 26.887156 L 57.539612 28.090671 L 55.132776 26.887156 Z M 57.539612 25.68364 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2100" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2106"> + <use + xlink:href="#glyph0-31" + x="651.492188" + y="169.377062" + id="use2102" /> + <use + xlink:href="#glyph0-32" + x="656.492188" + y="169.377062" + id="use2104" /> + </g> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 46.416761 26.887156 L 54.596057 26.887156 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2108" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 54.971057 26.887156 L 54.471057 27.137156 L 54.596057 26.887156 L 54.471057 26.637156 Z M 54.971057 26.887156 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2110" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 57.539612 23.866257 L 57.539612 25.146921 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2112" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 57.539612 25.521921 L 57.289612 25.021921 L 57.539612 25.146921 L 57.789612 25.021921 Z M 57.539612 25.521921 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2114" /> + <path + style="fill-rule:evenodd;fill:rgb(41.176471%,77.254903%,47.058824%);fill-opacity:0.592157;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 60.273401 28.318015 L 69.890979 28.318015 L 69.890979 30.218015 L 60.273401 30.218015 Z M 60.273401 28.318015 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2116" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2172"> + <use + xlink:href="#glyph0-1" + x="724.980469" + y="216.994249" + id="use2118" /> + <use + xlink:href="#glyph0-2" + x="733.869358" + y="216.994249" + id="use2120" /> + <use + xlink:href="#glyph0-3" + x="741.647135" + y="216.994249" + id="use2122" /> + <use + xlink:href="#glyph0-4" + x="748.313802" + y="216.994249" + id="use2124" /> + <use + xlink:href="#glyph0-5" + x="753.313802" + y="216.994249" + id="use2126" /> + <use + xlink:href="#glyph0-6" + x="756.924913" + y="216.994249" + id="use2128" /> + <use + xlink:href="#glyph0-7" + x="764.980469" + y="216.994249" + id="use2130" /> + <use + xlink:href="#glyph0-8" + x="773.036024" + y="216.994249" + id="use2132" /> + <use + xlink:href="#glyph0-9" + x="776.924913" + y="216.994249" + id="use2134" /> + <use + xlink:href="#glyph0-10" + x="789.424913" + y="216.994249" + id="use2136" /> + <use + xlink:href="#glyph0-11" + x="797.480469" + y="216.994249" + id="use2138" /> + <use + xlink:href="#glyph0-12" + x="801.09158" + y="216.994249" + id="use2140" /> + <use + xlink:href="#glyph0-17" + x="806.09158" + y="216.994249" + id="use2142" /> + <use + xlink:href="#glyph0-4" + x="814.147135" + y="216.994249" + id="use2144" /> + <use + xlink:href="#glyph0-18" + x="819.147135" + y="216.994249" + id="use2146" /> + <use + xlink:href="#glyph0-5" + x="824.424913" + y="216.994249" + id="use2148" /> + <use + xlink:href="#glyph0-6" + x="828.036024" + y="216.994249" + id="use2150" /> + <use + xlink:href="#glyph0-7" + x="836.09158" + y="216.994249" + id="use2152" /> + <use + xlink:href="#glyph0-15" + x="844.147135" + y="216.994249" + id="use2154" /> + <use + xlink:href="#glyph0-16" + x="848.313802" + y="216.994249" + id="use2156" /> + <use + xlink:href="#glyph0-17" + x="852.480469" + y="216.994249" + id="use2158" /> + <use + xlink:href="#glyph0-4" + x="860.536024" + y="216.994249" + id="use2160" /> + <use + xlink:href="#glyph0-18" + x="865.536024" + y="216.994249" + id="use2162" /> + <use + xlink:href="#glyph0-5" + x="870.813802" + y="216.994249" + id="use2164" /> + <use + xlink:href="#glyph0-6" + x="874.424913" + y="216.994249" + id="use2166" /> + <use + xlink:href="#glyph0-7" + x="882.480469" + y="216.994249" + id="use2168" /> + <use + xlink:href="#glyph0-19" + x="890.536024" + y="216.994249" + id="use2170" /> + </g> + <path + style="fill-rule:evenodd;fill:rgb(41.176471%,77.254903%,47.058824%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 51.584729 31.317234 L 63.49469 31.317234 L 63.49469 33.217234 L 51.584729 33.217234 Z M 51.584729 31.317234 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2174" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2234"> + <use + xlink:href="#glyph0-7" + x="565.378906" + y="276.978624" + id="use2176" /> + <use + xlink:href="#glyph0-25" + x="573.434462" + y="276.978624" + id="use2178" /> + <use + xlink:href="#glyph0-4" + x="581.21224" + y="276.978624" + id="use2180" /> + <use + xlink:href="#glyph0-24" + x="586.21224" + y="276.978624" + id="use2182" /> + <use + xlink:href="#glyph0-4" + x="592.601128" + y="276.978624" + id="use2184" /> + <use + xlink:href="#glyph0-18" + x="597.601128" + y="276.978624" + id="use2186" /> + <use + xlink:href="#glyph0-2" + x="602.878906" + y="276.978624" + id="use2188" /> + <use + xlink:href="#glyph0-6" + x="610.656684" + y="276.978624" + id="use2190" /> + <use + xlink:href="#glyph0-3" + x="618.71224" + y="276.978624" + id="use2192" /> + <use + xlink:href="#glyph0-33" + x="625.378906" + y="276.978624" + id="use2194" /> + <use + xlink:href="#glyph0-25" + x="629.823351" + y="276.978624" + id="use2196" /> + <use + xlink:href="#glyph0-18" + x="637.601128" + y="276.978624" + id="use2198" /> + <use + xlink:href="#glyph0-33" + x="642.878906" + y="276.978624" + id="use2200" /> + <use + xlink:href="#glyph0-23" + x="647.323351" + y="276.978624" + id="use2202" /> + <use + xlink:href="#glyph0-6" + x="655.378906" + y="276.978624" + id="use2204" /> + <use + xlink:href="#glyph0-26" + x="663.434462" + y="276.978624" + id="use2206" /> + <use + xlink:href="#glyph0-4" + x="670.378906" + y="276.978624" + id="use2208" /> + <use + xlink:href="#glyph0-5" + x="675.378906" + y="276.978624" + id="use2210" /> + <use + xlink:href="#glyph0-27" + x="678.990017" + y="276.978624" + id="use2212" /> + <use + xlink:href="#glyph0-6" + x="686.767795" + y="276.978624" + id="use2214" /> + <use + xlink:href="#glyph0-28" + x="694.823351" + y="276.978624" + id="use2216" /> + <use + xlink:href="#glyph0-17" + x="699.823351" + y="276.978624" + id="use2218" /> + <use + xlink:href="#glyph0-30" + x="707.878906" + y="276.978624" + id="use2220" /> + <use + xlink:href="#glyph0-15" + x="715.934462" + y="276.978624" + id="use2222" /> + <use + xlink:href="#glyph0-16" + x="720.101128" + y="276.978624" + id="use2224" /> + <use + xlink:href="#glyph0-17" + x="724.267795" + y="276.978624" + id="use2226" /> + <use + xlink:href="#glyph0-13" + x="732.323351" + y="276.978624" + id="use2228" /> + <use + xlink:href="#glyph0-20" + x="740.378906" + y="276.978624" + id="use2230" /> + <use + xlink:href="#glyph0-29" + x="748.434462" + y="276.978624" + id="use2232" /> + </g> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.2,0.2;stroke-miterlimit:10;" + d="M 62.568518 30.267624 L 60.621643 31.041648 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2236" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 60.529456 30.809421 L 60.15719 31.226413 L 60.714026 31.27407 Z M 60.529456 30.809421 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2238" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 57.539612 28.140671 L 57.539612 30.780906 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2240" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 57.539612 31.155906 L 57.289612 30.655906 L 57.539612 30.780906 L 57.789612 30.655906 Z M 57.539612 31.155906 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2242" /> + <path + style="fill-rule:evenodd;fill:rgb(41.176471%,77.254903%,47.058824%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 36.420081 31.317234 L 48.330042 31.317234 L 48.330042 33.217234 L 36.420081 33.217234 Z M 36.420081 31.317234 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2244" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2308"> + <use + xlink:href="#glyph0-7" + x="257.789062" + y="276.978624" + id="use2246" /> + <use + xlink:href="#glyph0-25" + x="265.844618" + y="276.978624" + id="use2248" /> + <use + xlink:href="#glyph0-4" + x="273.622396" + y="276.978624" + id="use2250" /> + <use + xlink:href="#glyph0-24" + x="278.622396" + y="276.978624" + id="use2252" /> + <use + xlink:href="#glyph0-4" + x="285.011285" + y="276.978624" + id="use2254" /> + <use + xlink:href="#glyph0-18" + x="290.011285" + y="276.978624" + id="use2256" /> + <use + xlink:href="#glyph0-2" + x="295.289062" + y="276.978624" + id="use2258" /> + <use + xlink:href="#glyph0-6" + x="303.06684" + y="276.978624" + id="use2260" /> + <use + xlink:href="#glyph0-3" + x="311.122396" + y="276.978624" + id="use2262" /> + <use + xlink:href="#glyph0-33" + x="317.789062" + y="276.978624" + id="use2264" /> + <use + xlink:href="#glyph0-25" + x="322.233507" + y="276.978624" + id="use2266" /> + <use + xlink:href="#glyph0-18" + x="330.011285" + y="276.978624" + id="use2268" /> + <use + xlink:href="#glyph0-33" + x="335.289062" + y="276.978624" + id="use2270" /> + <use + xlink:href="#glyph0-23" + x="339.733507" + y="276.978624" + id="use2272" /> + <use + xlink:href="#glyph0-6" + x="347.789062" + y="276.978624" + id="use2274" /> + <use + xlink:href="#glyph0-26" + x="355.844618" + y="276.978624" + id="use2276" /> + <use + xlink:href="#glyph0-4" + x="362.789062" + y="276.978624" + id="use2278" /> + <use + xlink:href="#glyph0-5" + x="367.789062" + y="276.978624" + id="use2280" /> + <use + xlink:href="#glyph0-27" + x="371.400174" + y="276.978624" + id="use2282" /> + <use + xlink:href="#glyph0-6" + x="379.177951" + y="276.978624" + id="use2284" /> + <use + xlink:href="#glyph0-28" + x="387.233507" + y="276.978624" + id="use2286" /> + <use + xlink:href="#glyph0-5" + x="392.233507" + y="276.978624" + id="use2288" /> + <use + xlink:href="#glyph0-6" + x="395.844618" + y="276.978624" + id="use2290" /> + <use + xlink:href="#glyph0-4" + x="403.900174" + y="276.978624" + id="use2292" /> + <use + xlink:href="#glyph0-13" + x="408.900174" + y="276.978624" + id="use2294" /> + <use + xlink:href="#glyph0-14" + x="416.955729" + y="276.978624" + id="use2296" /> + <use + xlink:href="#glyph0-15" + x="425.011285" + y="276.978624" + id="use2298" /> + <use + xlink:href="#glyph0-16" + x="429.177951" + y="276.978624" + id="use2300" /> + <use + xlink:href="#glyph0-17" + x="433.344618" + y="276.978624" + id="use2302" /> + <use + xlink:href="#glyph0-30" + x="441.400174" + y="276.978624" + id="use2304" /> + <use + xlink:href="#glyph0-29" + x="449.455729" + y="276.978624" + id="use2306" /> + </g> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 39.807776 27.917038 L 41.537659 30.848484 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2310" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 41.728284 31.171335 L 41.258948 30.86782 L 41.537659 30.848484 L 41.689417 30.613718 Z M 41.728284 31.171335 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2312" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 44.612464 27.917038 L 43.111682 30.834812 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2314" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 42.940198 31.16821 L 42.946643 30.609226 L 43.111682 30.834812 L 43.391175 30.837937 Z M 42.940198 31.16821 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2316" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.2,0.2;stroke-miterlimit:10;" + d="M 42.374964 23.831491 L 42.374964 30.654929 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2318" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 42.124964 30.654929 L 42.374964 31.154929 L 42.624964 30.654929 Z M 42.124964 30.654929 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2320" /> + <path + style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 30.784925 25.633054 L 33.293128 26.887156 L 30.784925 28.141257 L 28.276917 26.887156 Z M 30.784925 25.633054 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2322" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2330"> + <use + xlink:href="#glyph0-31" + x="114.308594" + y="169.377062" + id="use2324" /> + <use + xlink:href="#glyph0-32" + x="119.308594" + y="169.377062" + id="use2326" /> + <use + xlink:href="#glyph0-16" + x="130.141927" + y="169.377062" + id="use2328" /> + </g> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 30.784925 23.76489 L 30.784925 25.096335 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2332" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 30.784925 25.471335 L 30.534925 24.971335 L 30.784925 25.096335 L 31.034925 24.971335 Z M 30.784925 25.471335 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2334" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 37.381214 26.887156 L 33.830042 26.887156 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2336" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 33.455042 26.887156 L 33.955042 26.637156 L 33.830042 26.887156 L 33.955042 27.137156 Z M 33.455042 26.887156 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2338" /> + <path + style="fill-rule:evenodd;fill:rgb(61.176473%,63.921571%,97.647059%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 28.660706 35.381882 L 32.909339 34.542038 L 32.909339 36.642038 L 28.660706 36.642038 Z M 28.660706 35.381882 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2340" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2360"> + <use + xlink:href="#glyph0-5" + x="94.875" + y="351.869249" + id="use2342" /> + <use + xlink:href="#glyph0-6" + x="98.486111" + y="351.869249" + id="use2344" /> + <use + xlink:href="#glyph0-4" + x="106.541667" + y="351.869249" + id="use2346" /> + <use + xlink:href="#glyph0-13" + x="111.541667" + y="351.869249" + id="use2348" /> + <use + xlink:href="#glyph0-14" + x="119.597222" + y="351.869249" + id="use2350" /> + <use + xlink:href="#glyph0-28" + x="127.652778" + y="351.869249" + id="use2352" /> + <use + xlink:href="#glyph0-14" + x="132.652778" + y="351.869249" + id="use2354" /> + <use + xlink:href="#glyph0-13" + x="140.708333" + y="351.869249" + id="use2356" /> + <use + xlink:href="#glyph0-29" + x="148.763889" + y="351.869249" + id="use2358" /> + </g> + <path + style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 37.764612 35.581101 C 37.764612 36.05239 37.000354 36.434421 36.057776 36.434421 C 35.115198 36.434421 34.351136 36.05239 34.351136 35.581101 C 34.351136 35.109812 35.115198 34.727781 36.057776 34.727781 C 37.000354 34.727781 37.764612 35.109812 37.764612 35.581101 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2362" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.2,0.2;stroke-miterlimit:10;" + d="M 40.467932 33.267624 L 37.821448 34.655906 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2364" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 37.705237 34.434616 L 37.378675 34.888132 L 37.937659 34.87739 Z M 37.705237 34.434616 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2366" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 32.959534 35.587546 L 33.814417 35.585788 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2368" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 34.189417 35.585007 L 33.690003 35.835984 L 33.814417 35.585788 L 33.689026 35.335984 Z M 34.189417 35.585007 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2370" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 37.812268 35.570554 L 39.839807 35.558249 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2372" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 40.214807 35.555906 L 39.71637 35.808835 L 39.839807 35.558249 L 39.713245 35.308835 Z M 40.214807 35.555906 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2374" /> + <path + style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 40.726721 34.542624 L 44.835315 34.542624 L 44.107386 36.542624 L 39.998792 36.542624 Z M 40.726721 34.542624 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2376" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2394"> + <use + xlink:href="#glyph0-17" + x="330.019531" + y="342.48253" + id="use2378" /> + <use + xlink:href="#glyph0-30" + x="338.075087" + y="342.48253" + id="use2380" /> + <use + xlink:href="#glyph0-28" + x="346.130642" + y="342.48253" + id="use2382" /> + <use + xlink:href="#glyph0-34" + x="351.130642" + y="342.48253" + id="use2384" /> + <use + xlink:href="#glyph0-14" + x="356.963976" + y="342.48253" + id="use2386" /> + <use + xlink:href="#glyph0-13" + x="365.019531" + y="342.48253" + id="use2388" /> + <use + xlink:href="#glyph0-34" + x="373.075087" + y="342.48253" + id="use2390" /> + <use + xlink:href="#glyph0-29" + x="378.90842" + y="342.48253" + id="use2392" /> + </g> + <path + style="fill-rule:evenodd;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 50.59469 35.581101 C 50.59469 36.05239 49.830432 36.434421 48.887854 36.434421 C 47.945276 36.434421 47.181214 36.05239 47.181214 35.581101 C 47.181214 35.109812 47.945276 34.727781 48.887854 34.727781 C 49.830432 34.727781 50.59469 35.109812 50.59469 35.581101 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2396" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-dasharray:0.2,0.2;stroke-miterlimit:10;" + d="M 54.927893 33.267624 L 50.860901 34.825437 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2398" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 50.771643 34.591843 L 50.394104 35.004148 L 50.950354 35.058835 Z M 50.771643 34.591843 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2400" /> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 44.49762 35.554929 L 46.650745 35.56782 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2402" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 47.025745 35.569968 L 46.524182 35.817038 L 46.650745 35.56782 L 46.527307 35.317038 Z M 47.025745 35.569968 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2404" /> + <path + style="fill-rule:evenodd;fill:rgb(100%,70.19608%,14.509805%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 54.63805 34.542624 L 61.1693 34.542624 L 60.44137 36.542624 L 53.91012 36.542624 Z M 54.63805 34.542624 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2406" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2426"> + <use + xlink:href="#glyph0-17" + x="628.445313" + y="342.48253" + id="use2408" /> + <use + xlink:href="#glyph0-13" + x="636.500868" + y="342.48253" + id="use2410" /> + <use + xlink:href="#glyph0-20" + x="644.556424" + y="342.48253" + id="use2412" /> + <use + xlink:href="#glyph0-28" + x="652.611979" + y="342.48253" + id="use2414" /> + <use + xlink:href="#glyph0-34" + x="657.611979" + y="342.48253" + id="use2416" /> + <use + xlink:href="#glyph0-14" + x="663.445313" + y="342.48253" + id="use2418" /> + <use + xlink:href="#glyph0-13" + x="671.500868" + y="342.48253" + id="use2420" /> + <use + xlink:href="#glyph0-34" + x="679.556424" + y="342.48253" + id="use2422" /> + <use + xlink:href="#glyph0-29" + x="685.389757" + y="342.48253" + id="use2424" /> + </g> + <path + style="fill:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 50.577698 35.573484 L 53.753479 35.559421 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2428" /> + <path + style="fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" + d="M 54.128479 35.557663 L 53.629651 35.810007 L 53.753479 35.559421 L 53.627503 35.310007 Z M 54.128479 35.557663 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2430" /> + <path + style="fill-rule:evenodd;fill:rgb(100%, 100%, 100%);fill-opacity:1;stroke-width:0.20012599;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%, 0%, 0%);stroke-opacity:1;stroke-miterlimit:10;stroke-dasharray:none" + d="M 24.669495 38.169382 L 66.289417 38.169382 L 66.289417 40.751023 L 24.669495 40.751023 Z M 24.669495 38.169382 " + transform="matrix(20,0,0,20,-491.389899,-372.25483)" + id="path2432" /> + <g + style="fill:rgb(0%,0%,0%);fill-opacity:1;" + id="g2574"> + <use + xlink:href="#glyph1-1" + x="13.492188" + y="422.957954" + id="use2434" /> + <use + xlink:href="#glyph1-2" + x="27.658854" + y="422.957954" + id="use2436" /> + <use + xlink:href="#glyph1-3" + x="41.825521" + y="422.957954" + id="use2438" /> + <use + xlink:href="#glyph1-4" + x="49.325521" + y="422.957954" + id="use2440" /> + <use + xlink:href="#glyph1-5" + x="62.658854" + y="422.957954" + id="use2442" /> + <use + xlink:href="#glyph1-5" + x="72.381076" + y="422.957954" + id="use2444" /> + <use + xlink:href="#glyph1-4" + x="82.103299" + y="422.957954" + id="use2446" /> + <use + xlink:href="#glyph1-6" + x="94.881076" + y="422.957954" + id="use2448" /> + <use + xlink:href="#glyph1-7" + x="107.658854" + y="422.957954" + id="use2450" /> + <use + xlink:href="#glyph1-8" + x="116.825521" + y="422.957954" + id="use2452" + style="fill:#9ca1f8;fill-opacity:1" /> + <use + xlink:href="#glyph1-9" + x="130.436632" + y="422.957954" + id="use2454" + style="fill:#9ca1f8;fill-opacity:1" /> + <use + xlink:href="#glyph1-10" + x="144.047743" + y="422.957954" + id="use2456" /> + <use + xlink:href="#glyph1-11" + x="151.547743" + y="422.957954" + id="use2458" /> + <use + xlink:href="#glyph1-12" + x="158.492188" + y="422.957954" + id="use2460" /> + <use + xlink:href="#glyph1-13" + x="172.658854" + y="422.957954" + id="use2462" /> + <use + xlink:href="#glyph1-6" + x="182.103299" + y="422.957954" + id="use2464" /> + <use + xlink:href="#glyph1-2" + x="194.881076" + y="422.957954" + id="use2466" /> + <use + xlink:href="#glyph1-14" + x="209.047743" + y="422.957954" + id="use2468" /> + <use + xlink:href="#glyph1-15" + x="222.381076" + y="422.957954" + id="use2470" /> + <use + xlink:href="#glyph1-16" + x="239.047743" + y="422.957954" + id="use2472" + style="fill:#9ca1f8;fill-opacity:1" /> + <use + xlink:href="#glyph1-1" + x="245.71441" + y="422.957954" + id="use2474" + style="fill:#9ca1f8;fill-opacity:1" /> + <use + xlink:href="#glyph1-13" + x="259.881076" + y="422.957954" + id="use2476" + style="fill:#9ca1f8;fill-opacity:1" /> + <use + xlink:href="#glyph1-9" + x="269.325521" + y="422.957954" + id="use2478" + style="fill:#9ca1f8;fill-opacity:1" /> + <use + xlink:href="#glyph1-8" + x="282.936632" + y="422.957954" + id="use2480" + style="fill:#9ca1f8;fill-opacity:1" /> + <use + xlink:href="#glyph1-17" + x="296.547743" + y="422.957954" + id="use2482" /> + <use + xlink:href="#glyph1-3" + x="305.71441" + y="422.957954" + id="use2484" /> + <use + xlink:href="#glyph1-4" + x="313.21441" + y="422.957954" + id="use2486" /> + <use + xlink:href="#glyph1-18" + x="326.547743" + y="422.957954" + id="use2488" /> + <use + xlink:href="#glyph1-13" + x="338.21441" + y="422.957954" + id="use2490" /> + <use + xlink:href="#glyph1-6" + x="347.658854" + y="422.957954" + id="use2492" /> + <use + xlink:href="#glyph1-2" + x="360.436632" + y="422.957954" + id="use2494" /> + <use + xlink:href="#glyph1-14" + x="374.603299" + y="422.957954" + id="use2496" /> + <use + xlink:href="#glyph1-7" + x="387.936632" + y="422.957954" + id="use2498" /> + <use + xlink:href="#glyph1-19" + x="397.103299" + y="422.957954" + id="use2500" + style="fill:#000000;fill-opacity:1" /> + <use + xlink:href="#glyph1-20" + x="407.381076" + y="422.957954" + id="use2502" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-9" + x="421.547743" + y="422.957954" + id="use2504" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-21" + x="435.158854" + y="422.957954" + id="use2506" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-19" + x="448.769965" + y="422.957954" + id="use2508" + style="fill:#000000;fill-opacity:1" /> + <use + xlink:href="#glyph1-17" + x="459.047743" + y="422.957954" + id="use2510" /> + <use + xlink:href="#glyph1-11" + x="468.21441" + y="422.957954" + id="use2512" /> + <use + xlink:href="#glyph1-11" + x="475.158854" + y="422.957954" + id="use2514" /> + <use + xlink:href="#glyph1-22" + x="482.103299" + y="422.957954" + id="use2516" /> + <use + xlink:href="#glyph1-11" + x="498.769965" + y="422.957954" + id="use2518" /> + <use + xlink:href="#glyph1-1" + x="505.71441" + y="422.957954" + id="use2520" /> + <use + xlink:href="#glyph1-2" + x="519.881076" + y="422.957954" + id="use2522" /> + <use + xlink:href="#glyph1-3" + x="534.047743" + y="422.957954" + id="use2524" /> + <use + xlink:href="#glyph1-4" + x="541.547743" + y="422.957954" + id="use2526" /> + <use + xlink:href="#glyph1-5" + x="554.881076" + y="422.957954" + id="use2528" /> + <use + xlink:href="#glyph1-5" + x="564.603299" + y="422.957954" + id="use2530" /> + <use + xlink:href="#glyph1-4" + x="574.325521" + y="422.957954" + id="use2532" /> + <use + xlink:href="#glyph1-6" + x="587.103299" + y="422.957954" + id="use2534" /> + <use + xlink:href="#glyph1-7" + x="599.881076" + y="422.957954" + id="use2536" /> + <use + xlink:href="#glyph1-19" + x="609.047743" + y="422.957954" + id="use2538" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-8" + x="619.325521" + y="422.957954" + id="use2540" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-9" + x="632.936632" + y="422.957954" + id="use2542" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-19" + x="646.547743" + y="422.957954" + id="use2544" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-10" + x="656.825521" + y="422.957954" + id="use2546" /> + <use + xlink:href="#glyph1-11" + x="664.325521" + y="422.957954" + id="use2548" /> + <use + xlink:href="#glyph1-12" + x="671.269965" + y="422.957954" + id="use2550" /> + <use + xlink:href="#glyph1-13" + x="685.436632" + y="422.957954" + id="use2552" /> + <use + xlink:href="#glyph1-6" + x="694.881076" + y="422.957954" + id="use2554" /> + <use + xlink:href="#glyph1-2" + x="707.658854" + y="422.957954" + id="use2556" /> + <use + xlink:href="#glyph1-14" + x="721.825521" + y="422.957954" + id="use2558" /> + <use + xlink:href="#glyph1-15" + x="735.158854" + y="422.957954" + id="use2560" /> + <use + xlink:href="#glyph1-19" + x="751.825521" + y="422.957954" + id="use2562" /> + <use + xlink:href="#glyph1-20" + x="762.103299" + y="422.957954" + id="use2564" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-9" + x="776.269965" + y="422.957954" + id="use2566" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-21" + x="789.881076" + y="422.957954" + id="use2568" + style="fill:#ffb323;fill-opacity:1" /> + <use + xlink:href="#glyph1-19" + x="803.492188" + y="422.957954" + id="use2570" /> + <use + xlink:href="#glyph1-17" + x="813.769965" + y="422.957954" + id="use2572" /> + </g> + <text + xml:space="preserve" + style="font-size:14.6266px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;stroke:#cccccc;stroke-width:0.483685" + x="61.029861" + y="206.28314" + id="text2605"><tspan + sodipodi:role="line" + id="tspan2603" + x="61.029861" + y="206.28314" + style="stroke:#cccccc;stroke-width:0.483685">cast not required</tspan></text> + <text + id="text2609" + y="127.66325" + x="395.55035" + style="font-size:14.6266px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:sans-serif;letter-spacing:0px;word-spacing:0px;stroke:#cccccc;stroke-width:0.483685" + xml:space="preserve"><tspan + style="stroke:#cccccc;stroke-width:0.483685" + y="127.66325" + x="395.55035" + id="tspan2607" + sodipodi:role="line">only implements S8</tspan></text> + </g> +</svg> diff --git a/doc/neps/_static/dtype_hierarchy.svg b/doc/neps/_static/dtype_hierarchy.svg new file mode 100644 index 000000000..3bade3d0f --- /dev/null +++ b/doc/neps/_static/dtype_hierarchy.svg @@ -0,0 +1,935 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="432.64694mm" + height="374.31384mm" + viewBox="0 0 432.64693 374.31384" + version="1.1" + id="svg8" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="dtype_hierarchy.svg" + inkscape:export-filename="/home/sebastian/BIDS/dtypes/dtype_hierarchy.png" + inkscape:export-xdpi="43.502129" + inkscape:export-ydpi="43.502129"> + <defs + id="defs2"> + <marker + inkscape:isstock="true" + style="overflow:visible" + id="marker1380" + refX="0" + refY="0" + orient="auto" + inkscape:stockid="Arrow2Mend"> + <path + transform="scale(-0.6)" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + id="path1378" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow1Mstart" + orient="auto" + refY="0" + refX="0" + id="Arrow1Mstart" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path835" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="matrix(0.4,0,0,0.4,4,0)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow2Mend" + orient="auto" + refY="0" + refX="0" + id="Arrow2Mend" + style="overflow:visible" + inkscape:isstock="true" + inkscape:collect="always"> + <path + id="path856" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" + d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" + transform="scale(-0.6)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow1Send" + orient="auto" + refY="0" + refX="0" + id="Arrow1Send" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path844" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="matrix(-0.2,0,0,-0.2,-1.2,0)" + inkscape:connector-curvature="0" /> + </marker> + <marker + inkscape:stockid="Arrow1Lend" + orient="auto" + refY="0" + refX="0" + id="Arrow1Lend" + style="overflow:visible" + inkscape:isstock="true"> + <path + id="path832" + d="M 0,0 5,-5 -12.5,0 5,5 Z" + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1" + transform="matrix(-0.8,0,0,-0.8,-10,0)" + inkscape:connector-curvature="0" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.49497475" + inkscape:cx="-261.18562" + inkscape:cy="440.75659" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="3440" + inkscape:window-height="1376" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" + fit-margin-top="10" + fit-margin-left="10" + fit-margin-right="10" + fit-margin-bottom="10" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(1.2604327,40.771063)"> + <rect + style="opacity:1;fill:#ff9000;fill-opacity:1;stroke:none;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2120" + width="44.63401" + height="17.105249" + x="23.573442" + y="161.07759" + rx="2.843874" + ry="2.5766025" /> + <text + id="text2124" + y="172.67239" + x="30.910395" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + xml:space="preserve"><tspan + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + y="172.67239" + x="30.910395" + id="tspan2122" + sodipodi:role="line">DType</tspan></text> + <g + id="g1288"> + <rect + style="opacity:1;fill:#4dabcf;fill-opacity:1;stroke:none;stroke-width:1.37016749;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2146" + width="61.739262" + height="17.105249" + x="42.09428" + y="203.41173" + rx="2.843874" + ry="2.5766025" /> + <text + id="text2150" + y="215.00667" + x="56.400177" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="215.00667" + x="56.400177" + id="tspan2148" + sodipodi:role="line">Float64</tspan></text> + </g> + <g + id="g1283"> + <rect + ry="2.5766025" + rx="2.843874" + y="182.24493" + x="42.09428" + height="17.105249" + width="61.739262" + id="rect2154" + style="opacity:1;fill:#4dabcf;fill-opacity:1;stroke:none;stroke-width:1.37016749;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="61.425056" + y="194.36903" + id="text2158"><tspan + sodipodi:role="line" + id="tspan2156" + x="61.425056" + y="194.36903" + style="stroke-width:0.26458332">Int64</tspan></text> + </g> + <g + id="g1334" + transform="translate(46.037524,104.2459)"> + <path + inkscape:connector-curvature="0" + id="path827" + d="m 58.264755,86.658881 h 10.69078" + style="fill:none;stroke:#000000;stroke-width:1.16499996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" + sodipodi:nodetypes="cc" /> + </g> + <g + transform="translate(25.819071,143.16003)" + id="g2205"> + <rect + style="opacity:1;fill:none;fill-opacity:1;stroke:#9866cf;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2199" + width="44.63401" + height="17.105249" + x="92.475258" + y="39.0849" + rx="0" + ry="0" /> + <text + id="text2203" + y="50.67984" + x="99.81221" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="50.67984" + x="99.81221" + id="tspan2201" + sodipodi:role="line">>int64</tspan></text> + </g> + <g + id="g2213" + transform="translate(75.560923,143.16003)"> + <rect + ry="0" + rx="0" + y="39.0849" + x="92.475258" + height="17.105249" + width="44.63401" + id="rect2207" + style="opacity:1;fill:none;fill-opacity:1;stroke:#9866cf;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="99.81221" + y="50.67984" + id="text2211"><tspan + sodipodi:role="line" + id="tspan2209" + x="99.81221" + y="50.67984" + style="stroke-width:0.26458332"><int64</tspan></text> + </g> + <g + id="g2663" + transform="translate(-50.910137,157.97679)"> + <g + id="g2645" + transform="translate(96.947661,-32.563904)"> + <path + inkscape:connector-curvature="0" + id="path2643" + d="m 58.264755,86.658881 h 10.69078" + style="fill:none;stroke:#000000;stroke-width:1.16499996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" + sodipodi:nodetypes="cc" /> + </g> + <g + transform="translate(76.729209,6.3500001)" + id="g2653"> + <rect + style="opacity:1;fill:none;fill-opacity:1;stroke:#9866cf;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2647" + width="44.63401" + height="17.105249" + x="92.475258" + y="39.0849" + rx="0" + ry="0" /> + <text + id="text2651" + y="50.67984" + x="97.166374" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="50.67984" + x="97.166374" + id="tspan2649" + sodipodi:role="line">>float64</tspan></text> + </g> + <g + id="g2661" + transform="translate(126.47106,6.3500001)"> + <rect + ry="0" + rx="0" + y="39.0849" + x="92.475258" + height="17.105249" + width="44.63401" + id="rect2655" + style="opacity:1;fill:none;fill-opacity:1;stroke:#9866cf;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="96.637207" + y="50.67984" + id="text2659"><tspan + sodipodi:role="line" + id="tspan2657" + x="96.637207" + y="50.67984" + style="stroke-width:0.26458332"><float64</tspan></text> + </g> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.51278019px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="146.93526" + y="178.48193" + id="text2737"><tspan + sodipodi:role="line" + id="tspan2735" + x="146.93526" + y="178.48193" + style="fill:#9866cf;fill-opacity:1;stroke-width:0.26458332">Instances</tspan></text> + <text + id="text2675-3" + y="153.10063" + x="8.1723146" + style="font-style:normal;font-weight:normal;font-size:11.38954353px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458329" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.26458329" + y="153.10063" + x="8.1723146" + id="tspan2673-6" + sodipodi:role="line">Concrete Types:</tspan></text> + <text + id="text3215" + y="72.108665" + x="243.3298" + style="font-style:normal;font-weight:normal;font-size:8.51278019px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="fill:#9866cf;fill-opacity:1;stroke-width:0.26458332" + y="72.108665" + x="243.3298" + id="tspan3213" + sodipodi:role="line">Instances</tspan></text> + <g + id="g1293"> + <rect + style="opacity:1;fill:#4dabcf;fill-opacity:1;stroke:none;stroke-width:1.37016749;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2146-6" + width="61.739262" + height="17.105249" + x="42.297195" + y="224.75034" + rx="2.843874" + ry="2.5766025" /> + <text + id="text2150-7" + y="236.34528" + x="47.843731" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="236.34528" + x="47.843731" + id="tspan2148-5" + sodipodi:role="line">datetime64</tspan></text> + </g> + <g + transform="translate(-50.910137,179.14359)" + id="g1057"> + <g + transform="translate(96.947661,-32.563904)" + id="g1039"> + <path + sodipodi:nodetypes="cc" + style="fill:none;stroke:#000000;stroke-width:1.16499996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" + d="m 58.264755,86.658881 h 10.69078" + id="path1037" + inkscape:connector-curvature="0" /> + </g> + <g + id="g1047" + transform="translate(76.729209,6.3500001)"> + <rect + ry="0" + rx="0" + y="39.0849" + x="92.475258" + height="17.105249" + width="44.63401" + id="rect1041" + style="opacity:1;fill:none;fill-opacity:1;stroke:#9866cf;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="98.777588" + y="50.67984" + id="text1045"><tspan + sodipodi:role="line" + id="tspan1043" + x="98.777588" + y="50.67984" + style="stroke-width:0.26458332"><M8[s]</tspan></text> + </g> + <g + transform="translate(126.47106,6.3500001)" + id="g1055"> + <rect + style="opacity:1;fill:none;fill-opacity:1;stroke:#9866cf;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect1049" + width="44.63401" + height="17.105249" + x="92.475258" + y="39.0849" + rx="0" + ry="0" /> + <text + id="text1053" + y="50.67984" + x="96.000237" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="50.67984" + x="96.000237" + id="tspan1051" + sodipodi:role="line">>M8[ns]</tspan></text> + <rect + ry="0" + rx="0" + y="39.0849" + x="92.475258" + height="17.105249" + width="44.63401" + id="rect1207" + style="opacity:1;fill:none;fill-opacity:1;stroke:#9866cf;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <g + id="g1265" + style="fill:#9866cf;fill-opacity:1" + transform="matrix(2.2707534,0,0,2.2707534,-177.4572,-60.535544)"> + <circle + r="1.1358955" + cy="47.637524" + cx="141.20377" + id="path1256" + style="opacity:1;fill:#9866cf;fill-opacity:1;stroke:none;stroke-width:1.71979165;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <circle + style="opacity:1;fill:#9866cf;fill-opacity:1;stroke:none;stroke-width:1.71979165;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="circle1258" + cx="144.21054" + cy="47.637524" + r="1.1358955" /> + <circle + r="1.1358955" + cy="47.637524" + cx="147.21733" + id="circle1260" + style="opacity:1;fill:#9866cf;fill-opacity:1;stroke:none;stroke-width:1.71979165;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + </g> + </g> + </g> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:11.38954353px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458329" + x="8.1723146" + y="-22.317886" + id="text2802"><tspan + sodipodi:role="line" + id="tspan2800" + x="8.1723146" + y="-22.317886" + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.26458329">Concept:</tspan></text> + <ellipse + ry="11.626225" + rx="29.800554" + cy="-4.7734947" + cx="53.874973" + id="path2804" + style="opacity:1;fill:#ffc553;fill-opacity:1;stroke:none;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + x="28.792381" + y="-2.5054595" + id="text2808"><tspan + sodipodi:role="line" + id="tspan2806" + x="28.792381" + y="-2.5054595" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1">DTypeMeta</tspan></text> + <rect + ry="2.5766025" + rx="2.843874" + y="-14.100398" + x="96.558197" + height="17.105249" + width="44.63401" + id="rect2810" + style="opacity:1;fill:#ff9000;fill-opacity:1;stroke:none;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + x="104.69695" + y="-3.0399978" + id="text2814"><tspan + sodipodi:role="line" + id="tspan2812" + x="104.69695" + y="-3.0399978" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1">DType</tspan></text> + <g + transform="translate(25.655198,-91.784983)" + id="g2818"> + <path + sodipodi:nodetypes="cc" + style="fill:none;stroke:#000000;stroke-width:1.16499996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" + d="m 58.264755,86.658881 h 10.69078" + id="path2816" + inkscape:connector-curvature="0" /> + </g> + <g + id="g2846" + transform="translate(4.3286934,-30.623148)"> + <rect + ry="2.5766025" + rx="2.843874" + y="39.0849" + x="108.87944" + height="52.384823" + width="90.871613" + id="rect2840" + style="opacity:1;fill:none;fill-opacity:1;stroke:#ff9153;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:9.03164291px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="112.0525" + y="50.851093" + id="text2844"><tspan + id="tspan2842" + sodipodi:role="line" + x="112.0525" + y="50.851093" + style="stroke-width:0.26458332">AbstractDtypes:</tspan><tspan + sodipodi:role="line" + x="112.0525" + y="62.140648" + style="stroke-width:0.26458332" + id="tspan3068">• type hierarchy</tspan><tspan + sodipodi:role="line" + x="112.0525" + y="73.430199" + style="stroke-width:0.26458332" + id="tspan3078">• UFunc resolution</tspan><tspan + sodipodi:role="line" + x="112.0525" + y="84.719757" + style="stroke-width:0.26458332" + id="tspan3072">• may promote</tspan><tspan + sodipodi:role="line" + x="112.0525" + y="96.009308" + style="stroke-width:0.26458332" + id="tspan3066" /></text> + </g> + <g + transform="translate(20.732865,35.976636)" + id="g2854"> + <rect + style="opacity:1;fill:#4dabcf;fill-opacity:1;stroke:none;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2848" + width="91.940727" + height="47.306705" + x="92.475258" + y="39.0849" + rx="2.843874" + ry="2.5766025" /> + <text + id="text2852" + y="49.872658" + x="96.583473" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="49.872658" + x="96.583473" + id="tspan2850" + sodipodi:role="line">Concrete DTypes:</tspan><tspan + style="stroke-width:0.26458332" + y="60.811176" + x="96.583473" + sodipodi:role="line" + id="tspan3086">• casting/promotion</tspan><tspan + style="stroke-width:0.26458332" + y="71.749695" + x="96.583473" + sodipodi:role="line" + id="tspan3088">• UFunc signature</tspan></text> + </g> + <g + transform="translate(148.727,10.030009)" + id="g2858"> + <path + sodipodi:nodetypes="cc" + style="fill:none;stroke:#000000;stroke-width:1.16499996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" + d="m 58.264755,86.658881 h 10.69078" + id="path2856" + inkscape:connector-curvature="0" /> + </g> + <rect + style="opacity:1;fill:none;fill-opacity:1;stroke:#9866cf;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2860" + width="84.724449" + height="45.435818" + x="220.98373" + y="75.328812" + rx="0" + ry="0" /> + <text + id="text2864" + y="86.923752" + x="228.32069" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="86.923752" + x="228.32069" + id="tspan2862" + sodipodi:role="line">DType Instances</tspan><tspan + id="tspan3074" + style="stroke-width:0.26458332" + y="97.862267" + x="228.32069" + sodipodi:role="line">• Describe data</tspan><tspan + id="tspan3076" + style="stroke-width:0.26458332" + y="108.80079" + x="228.32069" + sodipodi:role="line">• `arr.dtype`</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="228.32069" + y="38.240372" + id="text3096"><tspan + sodipodi:role="line" + x="228.32069" + y="38.240372" + style="stroke-width:0.26458332" + id="tspan3094">(Cannot be instantiated)</tspan></text> + <text + id="text3223" + y="277.53149" + x="163.20908" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + id="tspan3221" + style="stroke-width:0.26458332" + y="277.53149" + x="163.20908" + sodipodi:role="line">Concrete Types form</tspan><tspan + style="stroke-width:0.26458332" + y="288.47" + x="163.20908" + sodipodi:role="line" + id="tspan3225">leaves of the tree;</tspan><tspan + id="tspan3248" + style="stroke-width:0.26458332" + y="299.40854" + x="163.20908" + sodipodi:role="line">the inheritance is abstract</tspan><tspan + id="tspan3270" + style="stroke-width:0.26458332" + y="310.34705" + x="163.20908" + sodipodi:role="line">similar to Python's abc.ABC. </tspan></text> + <g + id="g3084" + transform="translate(35.454183,8.4666671)"> + <g + transform="translate(111.41246,-60.58556)" + id="g3058"> + <path + sodipodi:nodetypes="cc" + style="fill:none;stroke:#000000;stroke-width:1.16499996;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Mend)" + d="m 58.264755,86.658881 h 10.69078" + id="path3056" + inkscape:connector-curvature="0" /> + </g> + <text + id="text3062" + y="33.375549" + x="177.81163" + style="font-style:normal;font-weight:normal;font-size:25.73707581px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="33.375549" + x="177.81163" + id="tspan3060" + sodipodi:role="line">x</tspan></text> + </g> + <path + sodipodi:nodetypes="ccc" + inkscape:connector-curvature="0" + id="path3217" + d="m 240.37519,140.22719 v 115.49978 l -21.73363,-0.0959" + style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cc" + style="fill:none;stroke:#000000;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 373.75879,132.43914 H 41.810036" + id="path3219" + inkscape:connector-curvature="0" /> + <g + id="g1383" + transform="translate(1.5875)"> + <g + transform="translate(282.50926,124.19261)" + id="g1309"> + <rect + ry="2.5766025" + rx="2.843874" + y="182.24493" + x="42.09428" + height="17.105249" + width="61.739262" + id="rect1303" + style="opacity:1;fill:#4dabcf;fill-opacity:1;stroke:none;stroke-width:1.37016749;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="61.425056" + y="194.36903" + id="text1307"><tspan + sodipodi:role="line" + id="tspan1305" + x="61.425056" + y="194.36903" + style="stroke-width:0.26458332">Int64</tspan></text> + </g> + <g + transform="translate(283.57834,60.501707)" + id="g1301"> + <rect + style="opacity:1;fill:#4dabcf;fill-opacity:1;stroke:none;stroke-width:1.37016749;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect1295" + width="61.739262" + height="17.105249" + x="42.09428" + y="203.41173" + rx="2.843874" + ry="2.5766025" /> + <text + id="text1299" + y="215.00667" + x="56.400177" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="215.00667" + x="56.400177" + id="tspan1297" + sodipodi:role="line">Float64</tspan></text> + </g> + <g + transform="translate(185.91751,182.30441)" + id="g2195"> + <rect + style="opacity:1;fill:none;fill-opacity:1;stroke:#ff9153;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect2187" + width="44.63401" + height="17.105249" + x="108.87944" + y="39.0849" + rx="2.843874" + ry="2.5766025" /> + <text + id="text2193" + y="50.321926" + x="113.11084" + style="font-style:normal;font-weight:normal;font-size:9.03164291px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="50.321926" + x="113.11084" + sodipodi:role="line" + id="tspan2191">Inexact</tspan></text> + </g> + <g + id="g2671" + transform="translate(172.15917,161.04236)"> + <rect + ry="2.5766025" + rx="2.843874" + y="39.0849" + x="108.87944" + height="17.105249" + width="44.63401" + id="rect2665" + style="opacity:1;fill:none;fill-opacity:1;stroke:#ff9153;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:9.03164291px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="112.0525" + y="50.851093" + id="text2669"><tspan + id="tspan2667" + sodipodi:role="line" + x="112.0525" + y="50.851093" + style="stroke-width:0.26458332">Numeric</tspan></text> + </g> + <g + id="g2701" + transform="translate(170.57168,118.51826)"> + <rect + ry="2.5766025" + rx="2.843874" + y="39.0849" + x="92.475258" + height="17.105249" + width="44.63401" + id="rect2695" + style="opacity:1;fill:#ff9153;fill-opacity:1;stroke:none;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="99.81221" + y="50.67984" + id="text2699"><tspan + sodipodi:role="line" + id="tspan2697" + x="99.81221" + y="50.67984" + style="stroke-width:0.26458332">DType</tspan></text> + </g> + <g + id="g2709" + transform="translate(201.26335,203.56648)"> + <rect + ry="2.5766025" + rx="2.843874" + y="39.0849" + x="108.87944" + height="17.105249" + width="44.63401" + id="rect2703" + style="opacity:1;fill:none;fill-opacity:1;stroke:#ff9153;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:9.03164291px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="112.0525" + y="50.321926" + id="text2707"><tspan + id="tspan2705" + sodipodi:role="line" + x="112.0525" + y="50.321926" + style="stroke-width:0.26458332">Floating</tspan></text> + </g> + <g + id="g2717" + transform="translate(185.38297,221.7489)"> + <rect + ry="2.5766025" + rx="2.843874" + y="63.42659" + x="108.87944" + height="17.105249" + width="44.63401" + id="rect2711" + style="opacity:1;fill:none;fill-opacity:1;stroke:#ff9153;stroke-width:1.16499996;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:9.03164291px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="113.11084" + y="74.663612" + id="text2715"><tspan + id="tspan2713" + sodipodi:role="line" + x="113.11084" + y="74.663612" + style="stroke-width:0.26458332">Integral</tspan></text> + </g> + <text + id="text2774" + y="149.89339" + x="244.4957" + style="font-style:normal;font-weight:normal;font-size:11.38954258px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458329" + xml:space="preserve"><tspan + style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.26458329" + y="149.89339" + x="244.4957" + id="tspan2772" + sodipodi:role="line">Abstract Types (Hierarchy):</tspan></text> + <g + transform="translate(238.74141,-45.88513)" + id="g1317"> + <rect + style="opacity:1;fill:#4dabcf;fill-opacity:1;stroke:none;stroke-width:1.37016749;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="rect1311" + width="61.739262" + height="17.105249" + x="42.297195" + y="224.75034" + rx="2.843874" + ry="2.5766025" /> + <text + id="text1315" + y="236.34528" + x="47.843731" + style="font-style:normal;font-weight:normal;font-size:8.75081348px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="236.34528" + x="47.843731" + id="tspan1313" + sodipodi:role="line">datetime64</tspan></text> + </g> + </g> + </g> +</svg> diff --git a/doc/neps/nep-0016-benchmark.py b/doc/neps/nep-0016-benchmark.py index af6059114..ec8e44726 100644 --- a/doc/neps/nep-0016-benchmark.py +++ b/doc/neps/nep-0016-benchmark.py @@ -1,5 +1,3 @@ -.. _NEP16: - import perf import abc import numpy as np diff --git a/doc/neps/nep-0042-new-dtypes.rst b/doc/neps/nep-0042-new-dtypes.rst new file mode 100644 index 000000000..1f476114f --- /dev/null +++ b/doc/neps/nep-0042-new-dtypes.rst @@ -0,0 +1,1344 @@ +======================================== +NEP 42 — Implementation of New DataTypes +======================================== + +:title: Extensible Datatypes for NumPy +:Author: Sebastian Berg +:Author: Marten van Kerkwijk +:Status: Draft +:Type: Standard +:Created: 2019-07-17 + + +.. note:: + + This NEP is part of a series of NEPs encompassing first information + about the previous dtype implementation and issues with it in + :ref:`NEP 40 <NEP40>`. + :ref:`NEP 41 <NEP41>` then provides an overview and generic design + choices for the refactor. NEPs 42 (this document) + and 43 go into the technical details of the internal and external + API changes related to datatypes and universal functions, respectively. + In some cases it may be necessary to consult the other NEPs for a full + picture of the desired changes and why these changes are necessary. + + +Abstract +-------- + +NEP 40 and 41 detailed the need for the creation of a new datatype system within +NumPy to better serve downstream use-cases and improve the maintainability +and the extensibility of NumPy. +A main issue with the current dtype API is that datatypes are written as +a single Python class with special instances for each of the actual datatypes. +While this certainly has been a practical approach in implementing numerical +datatypes, it does not allow to naturally split up logic. For example, +functions such as ``can_cast`` have explicit logic for each datatype. +Because of this monolithic code structure user-defined datatypes do not have +the same capabilities as NumPy datatypes have. +The current structure also makes understanding and modifying datatypes harder. +The current datatypes are not well encapsulated, so modifications targeting +a single datatype inevitably touch code involving others. +As detailed in NEP 41, the desired general design is to create classes for +each of the NumPy-provided datatypes, meaning that ``np.dtype("float64")`` +returns an instance of a ``Float64`` class which is a subclass of ``np.dtype``. +``np.dtype[float64]`` will also be used to denote this class. +This will allow moving all logic into special methods on the ``np.dtype`` +subclasses. This ``DType`` class would then serve as the central +extension point for adding new dtypes to NumPy. + +This document proposes the new API for the datatypes itself. +A second proposal NEP 43 details proposed changes to the universal +functions. +Note that only the implementation of both NEPs will provide the desired full +functionality. + + +.. note:: + + At this time this NEP is in a preliminary state. Both internal and + external API may be adapted based on user input or implementation needs. + The general design principles and choices, while provisional, should not + be expected to change dramatically. + + +Detailed Description +-------------------- + +NEP 41 layed out the creation of a class hierarchy for datatypes using the +new DType classes to provide all necessary implementations. +This NEP defines the specific choice of API necessary to define new DTypes. +Here, these are suggested as C-API slots; however, conceptually these +translate identically to Python methods. + +Additionally, the NEP proposes to implement the notion of *abstract* DTypes. +Further, we detail – in part – how the proposed methods (C-API slots) +enable all necessary use cases. + +Each section will begin with a short motivation of the issue or what +problem is addressed. This is followed by a description of the proposed +design choice, and then may list alternatives. + + +Nomenclature +"""""""""""" + +As a brief note on nomenclature, it should be noted that ``dtype`` normally +denotes the dtype *instance*, which is the object attached to a numpy array. +On the other hand the ``DType`` class is the subclass of ``np.dtype``. +On the C-level we currently use the name ``descriptor`` or ``descr`` +interchangeably with *dtype instance*. ``descriptor`` or ``descr`` will be +used in proposed C-API names to differentiate dtype instances from DType +classes more clearly. +Note that the notion of dtype class is currently represented mainly as +the ``dtype.num`` and ``dtype.char``. +Please see the :ref:`dtype hierarchy figure <hierarchy_figure>` for an +illustration of this distinction. + +There are currently classes in NumPy for numeric types e.g. +``np.float64``; however, +these are not DTypes but the corresponding scalar classes +(see also NEP 40 and 41 for discussion on why these are largely unrelated to +the proposed changes). + + +Proposed access to DType class +"""""""""""""""""""""""""""""" + +**Motivation:** + +Currently we often call ``np.dtype`` to create the dtype instance +corresponding to a given scalar type (e.g. ``np.dtype(np.int64)``). +Adding the DType classes may require a way to access the classes conveniently. + +**Description:** + +To avoid duplication, but also to expose the classes conveniently to users +we propose the addition of:: + + np.dtype[np.int64] + +as a class getter. This can work both for user and NumPy DTypes, +although, in many cases a library may choose to provide a more direct +way to access the specific DType class. +This method may initially be limited to concrete DTypes. +The main reason for this choice is to provide a single +clear and future-proof way to find the DType class given the +Python (scalar) class. + +This should not be a common operation, so providing this class getter reduces +the pressure of adding the new DType classes into the namespace. + +*Note: This is currently a possible extension and not yet decided.* + + +Hierarchy of DTypes and Abstract DTypes +""""""""""""""""""""""""""""""""""""""" + +**Motivation:** +The creation of DType classes has already been decided in NEP 41. +Here we discuss the notion of **abstract** DTypes. +There are multiple reasons for this: + +1. It allows the definition of a class hierarchy, in principle allowing checks like + ``isinstance(np.dtype("float64"), np.inexact)``. + **This hierarchy may be a prerequisite to implementing dispatching + for universal functions (NEP 43)** +2. Abstract DTypes can enable code such as + ``arr.astype(Complex)`` to express the desire to cast to a + complex data type of unspecified precision. +3. It anticipates the creation of families of DTypes by users. + For example allowing the creation of an abstract ``Unit`` class with a concrete + ``Float64Unit``. In which case ``Unit(np.float64, "m")`` could be + identical to ``Float64Unit("m")``. + +A very concrete example is the current Pandas ``Categorical`` DType, +which may benefit from abstraction to allow the differentiation of +a categorical of integer values and one of general object values. +The reason for this is that we may want to reject +``common_dtype(CategoricalInt64, String)``, but accept +``common_dtype(CategoricalObject, String)`` to be the ``object`` DType. +The current Pandas ``Categorical`` DType combines both and must remain +representable. The solution is thus to make ``Categorical`` abstract with +the two subclasses ``CategoricalInt64`` and ``CategoricalObject`` +distinguishing the two. + + +**Description:** + +The figure below shows the proposed datatype hierarchy. +It should be noted that abstract DTypes are distinct in two ways: + +1. They do not have instances. Instantiating an abstract DType has to return + a concrete subclass or raise an error (default, and possibly enforced + initially). +2. Unlike concrete DTypes, abstract DTypes can be superclasses, they may also + serve like Python's abstract base classes (ABC). + (It may be possible to simply use/inherit from Python ABCs.) + +These two rules are identical to the type choices made for example in the +`Julia language <https://docs.julialang.org/en/v1/manual/types/#man-abstract-types-1>`_. +It allows for the creation of a datatype hierarchy, but avoids issues with +subclassing concrete DTypes directly. +For example, logic such as ``can_cast`` does not cleanly inherit from a +``Int64`` to a ``Datetime64`` even though the ``Datetime64`` could be seen +as an integer with only a unit attached (and thus implemented as a subclass). + +The main consequence for the DType implementer is that concrete DTypes can +never be subclasses of existing concrete DTypes. +End-users would not notice or need to know about this distinction. +However, subclassing may be a possible mechanism to extend the datatypes +in the future to allow specialized implementations for existing dtypes +such as a GPU float64 subclassing a NumPy float64. + +The combination of (initially) rejecting subclassing of concrete DTypes +while allowing it for abstract ones allows the transparent definition of +a class hierarchy, while avoiding potential issues with subclassing and +especially inheritance. + +As a technical implementation detail: the DType class will require C-side +storage of methods and additional information. +This requires the creation of a ``DTypeMeta`` class. +Each ``DType`` class is thus an instance of ``DTypeMeta`` with a well-defined +and extensible interface. +The end-user will not need to be aware of this. + +.. _hierarchy_figure: +.. figure:: _static/dtype_hierarchy.svg + :figclass: align-center + + +Methods/Slots defined for each DType +"""""""""""""""""""""""""""""""""""" + +NEP 41 detailed that all logic should be defined through special methods +on the DTypes. +This section will list a specific set of such methods (in the form of +Python methods). +The C-side equivalent slot signature will be summarized below after proposing +the general C-API for defining new Datatypes. +Note that while the slots are defined as special Python methods here, this is +for the readers convenience and *not* meant to imply the identical exposure +as a Python API. +This will need to be proposed in a separate, later, NEP. + +Some of the methods may be similar or even reuse existing Python slots. +User-defined DType classes are discouraged from defining or using Python's +special slots without consulting the NumPy developers, in order to allow +defining them later. +For example ``dtype1 & dtype2`` could be a shorthand for +``np.common_dtype(dtype1, dtype2)``, and comparisons should be defined mainly +through casting logic. + + +Additional Information +^^^^^^^^^^^^^^^^^^^^^^ + +In addition to the more detailed methods below, the following general +information is currently provided and will be defined on the class: + +* ``cls.parametric`` (see also `NEP 40 <NEP40>`_): + + * Parametric will be a flag in the (private) C-API. However, the + Python API will instead use a ``ParametricDType`` class from + which to inherit. (This is similar to Python's type flags, which include + flags for some basic subclasses such as subclasses of ``float`` or ``tuple``) + * This flag is mainly to simplify DType creation and casting and + allow for performance tweaks. + * DTypes which are not parametric must define a canonical dtype instance + which should be a singleton. + * Parametric dtypes require some additional methods (below). + +* ``self.canonical`` method (Alternative: new instance attribute) + + * Instead of byteorder, we may want a ``canonical`` flag (reusing the + ISNBO flag – "is native byte order" seems possible here). + This flag signals that the data are stored in the default/canonical way. + In practice this is always an NBO check, but generalization should be possible. + A potential use-case is a complex-conjugated instance of Complex which + stores ``real - imag`` instead of ``real + imag`` and is thus not + the canonical storage. + +* ``ensure_canonical(self) -> dtype`` return a new dtype (or ``self``). + The returned dtype must have the ``canonical`` flag set. + +* ``DType.type`` is the associated scalar type. ``dtype.type`` will be a + class attribute and the current ``dtype.type`` field will be considered + deprecated. This may be relaxed if a use-case arises. + +Additionally, existing methods (and C-side fields) will be provided. +However, the fields ``kind`` and and ``char`` will be set to ``\0`` +(NULL character) on the C-side. +While discouraged, except for NumPy builtin types, ``kind`` both will return +the ``__qualname__`` of the object to ensure uniqueness for all DTypes. +(the replacement for ``kind`` will be to use ``isinstance`` checks). + +Another example of methods that should be moved to the DType class are the +various sorting functions, which shall be implemented by defining a method: + +* ``dtype_get_sort_function(self, sortkind="stable") -> sortfunction`` + +which must return ``NotImplemented`` if the given ``sortkind`` is not known. +Similarly, any function implemented previously which cannot be removed will +be implemented as a special method. +Since these methods can be deprecated and new (renamed) replacements added, +the API is not defined here and it is acceptable if it changes over time. + +For some of the current "methods" defined on the dtype, including sorting, +a long term solution may be to instead create generalized ufuncs to provide +the functionality. + +**Alternatives:** + +Some of these flags could be implemented by inheriting +for example from a ``ParametricDType`` class. However, on the C-side as +an implementation detail it seems simpler to provide a flag. +This does not preclude the possibility of creating a ``ParametricDType`` +to Python to represent the same thing. + +**Example:** + +The ``datetime64`` DType is considered parametric, due to its unit, and +unlike a float64 has no canonical representation. The associated ``type`` +is the ``np.datetime64`` scalar. + + +**Issues and Details:** + +A potential DType such as ``Categorical`` will not be required to have a clear type +associated with it. Instead, the ``type`` may be ``object`` and the +categoircal's values are arbitrary objects. +Unlike with well-defined scalars, this ``type`` cannot +not be used for the dtype discovery necessary for coercion +(compare section `DType Discovery during Array Coercion`_). + + +Coercion to and from Python Objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Motivation:** + +When storing a single value in an array or taking it out of the array, +it is necessary to coerce (convert) it to and from the low-level +representation inside the array. + +**Description:** + +Coercing to and from Python scalars requires two to three methods: + +1. ``__dtype_setitem__(self, item_pointer, value)`` +2. ``__dtype_getitem__(self, item_pointer, base_obj) -> object`` + The ``base_obj`` should be ignored normally, it is provided *only* for + memory management purposes, pointing to an object owning the data. + It exists only to allow support of structured datatypes with subarrays + within NumPy, which (currently) return views into the array. + The function returns an equivalent Python scalar (i.e. typically a NumPy + scalar). +3. ``__dtype_get_pyitem__(self, item_pointer, base_obj) -> object`` + (initially hidden for new-style user-defined datatypes, may be exposed + on user request). This corresponds to the ``arr.item()`` method which + is also used by ``arr.tolist()`` and returns e.g. Python floats instead of + NumPy floats. + +(The above is meant for C-API. A Python-side API would have to use byte +buffers or similar to implement this, which may be useful for prototyping.) + +These largely correspond to the current definitions. When a certain scalar +has a known (different) dtype, NumPy may in the future use casting instead +of ``__dtype_setitem__``. +A user datatype is (initially) expected to implement ``__dtype_setitem__`` +for its own ``DType.type`` and all basic Python scalars it wishes to support +(e.g. integers, floats, datetime). +In the future a function "``known_scalartype``" may be added to allow a user +dtype to signal which Python scalars it can store directly. + + +**Implementation:** + +The pseudo-code implementation for setting a single item in an array +from an arbitrary Python object ``value`` is (note that some of the +functions are only defined below):: + + def PyArray_Pack(dtype, item_pointer, value): + DType = type(dtype) + if DType.type is type(value) or DType.known_scalartype(type(value)): + return dtype.__dtype_setitem__(item_pointer, value) + + # The dtype cannot handle the value, so try casting: + arr = np.array(value) + if arr.dtype is object or arr.ndim != 0: + # not a numpy or user scalar; try using the dtype after all: + return dtype.__dtype_setitem__(item_pointer, value) + + arr.astype(dtype) + item_pointer.write(arr[()]) + +where the call to ``np.array()`` represents the dtype discovery and is +not actually performed. + +**Example:** + +Current ``datetime64`` returns ``np.datetime64`` scalars and can be assigned +from ``np.datetime64``. +However, the datetime ``__dtype_setitem__`` also allows assignment from +date strings ("2016-05-01") or Python integers. +Additionally the datetime ``__dtype_get_pyitem__`` function actually returns +Python ``datetime.datetime`` object (most of the time). + + +**Alternatives:** + +This may be seen as simply a cast to and from the ``object`` dtype. +However, it seems slightly more complicated. This is because +in general a Python object could itself be a zero-dimensional array or +scalar with an associated DType. +Thus, representing it as a normal cast would either require that: + +* The implementor handles all Python classes, even those for which + ``np.array(scalar).astype(UserDType)`` already works because + ``np.array(scalar)`` returns, say, a datetime64 array. +* The cast is actually added between a typed-object to dtype. And even + in this case a generic fallback (for example ``float64`` can use + ``float(scalar)`` to do the cast) is also necessary. + +It is certainly possible to describe the coercion to and from Python objects +using the general casting machinery. However, it seems special enough to +handle specifically. + + +**Further Issues and Discussion:** + +The setitem function currently duplicates some code, such as coercion +from a string. ``datetime64`` allows assignment from string, but the same +conversion also occurs for casts from the string dtype to ``datetime64``. +In the future, we may expose a way to signal whether a conversion is known, +and otherwise a normal cast is made so that the item is effectively set to ``np.array(scalar).astype(requested_dtype)``. + +There is a general issue about the handling of subclasses. We anticipate to not +automatically detect the dtype for ``np.array(float64_subclass)`` to be +float64. The user can still provide ``dtype=np.float64``. However, the above +"assign by casting" using ``np.array(scalar_subclass).astype(requested_dtype)`` +will fail. + +.. note:: + + This means that ``np.complex256`` should not use ``__float__`` in its + ``__dtype_setitem__`` method in the future unless it is a known floating + point type. If the scalar is a subclass of a different high precision + floating point type (e.g. ``np.float128``) then this will lose precision. + + +DType Discovery during Array Coercion +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +An important step in the usage of NumPy arrays is the creation of the array +itself from collections of generic Python objects. + +**Motivation:** + +Although the distinction is not clear currently, there are two main needs:: + + np.array([1, 2, 3, 4.]) + +needs to guess the correct dtype based on the Python objects inside. +Such an array may include a mix of datatypes, as long as they can be clearly +promoted. +Currently not clearly distinct (but partially existing for strings) is the +use case of:: + + # np.dtype[np.str_] can also be spelled np.str_ or "S" (which works today) + np.array([object(), None], dtype=np.dtype[np.str_]) + +which forces each object to be interpreted as string. This is anticipated +to be useful for example for categorical datatypes:: + + np.array([1, 2, 1, 1, 2], dtype=Categorical) + +to allow the discovery the of all unique values. +(For NumPy ``datetime64`` this is also currently used to allow string input.) + +There are three further issues to consider: + +1. It may be desirable that datatypes can be created which are associated + to normal Python scalars (such as ``datetime.datetime``), which do not + have a ``dtype`` attribute already. +2. In general, a datatype could represent a sequence, however, NumPy currently + assumes that sequences are always collections of elements (the sequence cannot be an + element itself). An example for this is would be a ``vector`` DType. +3. An array may itself contain arrays with a specific dtype (even + general Python objects). For example: + ``np.array([np.array(None, dtype=object)], dtype=np.String)`` + poses the issue of how to handle the included array. + +Some of these difficulties arise due to the fact that finding the correct shape +of the output array and finding the correct datatype are closely related. + +**Implementation:** + +There are two distinct cases given above: First, when the user has provided no +dtype information, and second when the user provided a DType class – +a notion that is currently represented e.g. by the parametric instance ``"S"`` +representing a string of any length. + +In the first case, it is necessary to establish a mapping from the Python type(s) +of the constituent elements to the DType class. +When the DType class is known, the correct dtype instance still needs to be found. +This shall be implemented by leveraging two pieces of information: + +1. ``DType.type``: The current type attribute to indicate which Python scalar + type is associated with the DType class (this is a *class* attribute that always + exists for any datatype and is not limited to array coercion). +2. The reverse lookup will remain hardcoded for the basic Python types initially. + Otherwise the ``type`` attribute will be used, and at least initially may + enforce deriving the scalar from a NumPy-provided scalar base class. + This method may be expanded later (see alternatives). +3. ``__discover_descr_from_pyobject__(cls, obj) -> dtype``: A classmethod that + returns the correct descriptor given the input object. + *Note that only parametric DTypes have to implement this*, most datatypes + can simply use a default (singleton) dtype instance which is found only + based on the ``type(obj)`` of the Python object. + +The Python type which is already associated with a DType through the +``DType.type`` attribute maps from the DType to the Python type. +A DType may choose to automatically discover from this Python type. +This will be achieved using a global a mapping (dictionary-like) of:: + + known_python_types[type] = DType + +To anticipate the possibility of creating both a Python type (``pytype``) +and ``DType`` dynamically, and thus the potential desire to delete them again, +this mapping should generally be weak. +This requires that the ``pytype`` holds on to the ``DType`` explicitly. +Thus, in addition to building the global mapping, NumPy will store +the ``DType`` as ``pytype.__associated_array_dtype__`` in the Python type. +This does *not* define the mapping and should *not* be accessed directly. +In particular potential inheritance of the attribute does not mean that +NumPy will use the superclasses ``DType`` automatically. +A new ``DType`` must be created for the subclass. + +.. note:: + + Python integers do not have a clear/concrete NumPy type associated with + them right now. This is because during array coercion NumPy currently + finds the first type capable of representing their value in the list + of `long`, `unsigned long`, `int64`, `unsigned int64`, and `object` + (on many machines `long` is 64 bit). + + Instead they will need to be implemented using an + ``AbstractPyInt``. This DType class can then provide + ``__discover_descr_from_pyobject__`` and return the actual dtype which + is e.g. ``np.dtype("int64")``. + For dispatching/promotion in ufuncs, it will also be necessary + to dynamically create ``AbstractPyInt[value]`` classes (creation can be + cached), so that they can provide the current value based promotion + functionality provided by ``np.result_type(python_integer, array)`` [1]_. + +To allow for a DType to accept specific inputs as known scalars, we will +initially use a ``known_scalar_type`` method. +This allows discovery of a ``vector`` as a scalar (element) instead of +a sequence (for the command ``np.array(vector, dtype=VectorDType)``) +even when ``vector`` is itself a sequence or even an array subclass. +This will *not* be public API initially, but may be made public at a later +time. + +This will work similar to the following pseudo-code:: + + def find_dtype(array_like): + common_dtype = None + for element in array_like: + # default to object dtype, if unknown + DType = known_python_types.get(type(element), np.dtype[object]) + dtype = DType.__discover_descr_from_pyobject__(element) + + if common_dtype is None: + common_dtype = dtype + else: + common_dtype = np.promote_types(common_dtype, dtype) + +In practice, we have to find out whether an element is actually a sequence. +This means that instead of using the ``object`` dtype directly, we have to +check whether or not it is a sequence. + +The full algorithm (without user provided dtype) thus looks more like:: + + def find_dtype_recursive(array_like, dtype=None): + """ + Recursively find the dtype for a nested sequences (arrays are not + supported here). + """ + DType = known_python_types.get(type(element), None) + + if DType is None and is_array_like(array_like): + # Code for a sequence, an array_like may have a DType we + # can use directly: + for element in array_like: + dtype = find_dtype_recursive(element, dtype=dtype) + return dtype + + elif DType is None: + DType = np.dtype[object] + + # Same as above + +If the user provides ``DType``, then this DType will be tried first, and the +``dtype`` may need to be cast before the promotion is performed. + +**Limitations:** + +The above issue 3. is currently (sometimes) supported by NumPy so that +the values of an included array are inspected. +Support in those cases may be kept for compatibility, however, +it will not be exposed to user datatypes. +This means that if e.g. an array with a parametric string dtype is coerced above +(or cast) to an array of a fixed length string dtype (with unknown length), +this will result in an error. +Such a conversion will require passing the correct DType (fixed length of the +string) or providing a utility function to the user. + +The use of a global type map means that an error or warning has to be given +if two DTypes wish to map to the same Python type. In most cases user +DTypes should only be implemented for types defined within the same library to +avoid the potential for conflicts. +It will be the DType implementor's responsibility to be careful about this and use +the flag to disable registration when in doubt. + +**Alternatives:** + +The above proposes to add a global mapping, however, initially limiting it +to types deriving from a NumPy subclass (and a fixed set of Python types). +This could be relaxed in the future. +Alternatively, we could rely on the scalar belonging to the user dtype to +implement ``scalar.__associated_array_dtype__`` or similar. + +Initially, the exact implementation shall be *undefined*, if +scalars will have to derive from a NumPy scalar, they will also have +a ``.__associated_array_dtype__`` attribute. +At this time, a future update may to use this instead of a global mapping, +however, it makes NumPy a hard dependency for the scalar class. + +An initial alternative suggestion was to use a two-pass approach instead. +The first pass would only find the correct DType class, and the second pass +would then find correct dtype instance (the second pass is often not necessary). +The advantage of this is that the DType class information is vital for universal +functions to decide which loop to execute. +The first pass would provide the full information necessary for value-based +casting currently implemented for scalars, giving even the possibility of +expanding it to e.g. list inputs ``np.add(np.array([8], dtype="uint8"), [4])`` +giving a ``uint8`` result. +This is mainly related to the question to how the common dtype is found above. +It seems unlikely that this is useful, and similar to a global, could be +added later if deemed necessary. + +**Further Issues and Discussion:** + +While it is possible to create e.g. a DType such as Categorical, array, +or vector which can only be used if `dtype=DType` is provided, if this +is necessary these will not roundtrip correctly when converted back +and forth:: + + np.array(np.array(1, dtype=Categorical)[()]) + +requires to pass the original ``dtype=Categorical`` or returns an array +with dtype ``object``. +While a general limitation, the round-tripping shall always be possible if +``dtype=old_dtype`` is provided. + +**Example:** + +The current datetime DType requires a ``__discover_descr_from_pyobject__`` +which returns the correct unit for string inputs. This allows it to support +the current:: + + np.array(["2020-01-02", "2020-01-02 11:24"], dtype="M8") + +By inspecting the date strings. Together with the below common dtype +operation, this allows it to automatically find that the datetime64 unit +should be "minutes". + + +Common DType Operations +^^^^^^^^^^^^^^^^^^^^^^^ + +Numpy currently provides functions like ``np.result_type`` and +``np.promote_types`` for determining common types. +These differ in that ``np.result_type`` can take arrays and scalars as input +and implements value based promotion [1]_. + +To distinguish between the promotion occurring during universal function application, +we will call it "common type" operation here. + +**Motivation:** +Common type operations are vital for array coercion when different +input types are mixed. +They also provide the logic currently used to decide the output dtype of +``np.concatenate()`` and on their own are quite useful. + +Furthermore, common type operations may be used to find the correct dtype +to use for functions with different inputs (including universal functions). +This includes an interesting distinction: + +1. Universal functions use the DType classes for dispatching, they thus + require the common DType class (as a first step). + While this can help with finding the correct loop to execute, the loop + may not need the actual common dtype instance. + (Hypothetical example: + ``float_arr + string_arr -> string``, but the output string length is + not the same as ``np.concatenate(float_arr, string_arr)).dtype``.) +2. Array coercion and concatenation require the common dtype *instance*. + +**Implementation:** +The implementation of the common dtype (instance) determination +has some overlap with casting. +Casting from a specific dtype (Float64) to a String needs to find +the correct string length (a step that is mainly necessary for parametric dtypes). + +We propose the following implementation: + +1. ``__common_dtype__(cls, other : DTypeMeta) -> DTypeMeta`` answers what the common + DType class is given two DType class objects. + It may return ``NotImplemented`` to defer to ``other``. + (For abstract DTypes, subclasses get precedence, concrete types are always + leaves, so always get preference or are tried from left to right). +2. ``__common_instance__(self: SelfT, other : SelfT) -> SelfT`` is used when + two instances of the same DType are given. + For builtin dtypes (that are not parametric), this + currently always returns ``self`` (but ensures native byte order). + This is to preserve metadata. We can thus provide a default implementation + for non-parametric user dtypes. + +These two cases do *not* cover the case where two different dtype instances +need to be promoted. For example `">float64"` and `"S8"`. +The solution is partially "outsourced" to the casting machinery by +splitting the operation up into three steps: + +1. ``Float64.__common_dtype__(type(>float64), type(S8))`` + returns `String` (or defers to ``String.__common_dtype__``). +2. The casting machinery provides the information that `">float64"` casts + to `"S32"` (see below for how casting will be defined). +3. ``String.__common_instance__("S8", "S32")`` returns the final `"S32"`. + +The main reason for this is to avoid the need to implement +identical functionality multiple times. +The design (together with casting) naturally separates the concerns of +different Datatypes. +In the above example, Float64 does not need to know about the cast. +While the casting machinery (``CastingImpl[Float64, String]``) +could include the third step, it is not required to do so and the string +can always be extended (e.g. with new encodings) without extending the +``CastingImpl[Float64, String]``. + +This means the implementation will work like this:: + + def common_dtype(DType1, DType2): + common_dtype = type(dtype1).__common_dtype__(type(dtype2)) + if common_dtype is NotImplemented: + common_dtype = type(dtype2).__common_dtype__(type(dtype1)) + if common_dtype is NotImplemented: + raise TypeError("no common dtype") + return common_dtype + + def promote_types(dtype1, dtyp2): + common = common_dtype(type(dtype1), type(dtype2)) + + if type(dtype1) is not common: + # Find what dtype1 is cast to when cast to the common DType + # by using the CastingImpl as described below: + castingimpl = get_castingimpl(type(dtype1), common) + safety, (_, dtype1) = castingimpl.adjust_descriptors((dtype1, None)) + assert safety == "safe" # promotion should normally be a safe cast + + if type(dtype2) is not common: + # Same as above branch for dtype1. + + if dtype1 is not dtype2: + return common.__common_instance__(dtype1, dtype2) + +Some of these steps may be optimized for non-parametric DTypes. + +**Note:** + +A currently implemented fallback for the ``__common_dtype__`` operation +is to use the "safe" casting logic. +Since ``int16`` can safely cast to ``int64``, it is clear that +``np.promote_types(int16, int64)`` should be ``int64``. + +However, this cannot define all such operations, and will fail for example for:: + + np.promote_types("int64", "float32") -> np.dtype("float64") + +In this design, it is the responsibility of the DType author to ensure that +in most cases a safe-cast implies that this will be the result of the +``__common_dtype__`` method. + +Note that some exceptions may apply. For example casting ``int32`` to +a (long enough) string is – at least at this time – considered "safe". +However ``np.promote_types(int32, String)`` will *not* be defined. + +**Alternatives:** + +The use of casting for common dtype (instance) determination neatly separates +the concerns and allows for a minimal set of duplicate functionality +being implemented. +In cases of mixed DType (classes), it also adds an additional step +to finding the common dtype. +The common dtype (of two instances) could thus be implemented explicitly to avoid +this indirection, potentially only as a fast-path. +The above suggestion assumes that this is, however, not a speed relevant path, +since in most cases, e.g. in array coercion, only a single Python type (and thus +dtype) is involved. +The proposed design hinges in the implementation of casting to be +separated into its own ufunc-like object as described below. + +In principle common DType could be defined only based on "safe casting" rules, +if we order all DTypes and find the first one both can cast to safely. +However, the issue with this approach is that a newly added DType can change +the behaviour of an existing program. For example, a new ``int24`` would be +the first valid common type for ``int16`` and ``uint16``, demoting the currently +defined behaviour of ``int32``. +This API extension could be allowed in the future, while adding it may be +more involved, the current proposal for defining casts is fully opaque in +this regard and thus extensible. + +**Example:** + +``object`` always chooses ``object`` as the common DType. For ``datetime64`` +type promotion is defined with no other datatype, but if someone were to +implement a new higher precision datetime, then:: + + HighPrecisionDatetime.__common_dtype__(np.dtype[np.datetime64]) + +would return ``HighPrecisionDatetime``, and the below casting may need to +decide how to handle the datetime unit. + + +Casting +^^^^^^^ + +Maybe the most complex and interesting operation which is provided +by DTypes is the ability to cast from one dtype to another. +The casting operation is much like a typical function (universal function) on +arrays converting one input to a new output. +There are mainly two distinctions: + +1. Casting always requires an explicit output datatype to be given. +2. The NumPy iterator API requires access to lower-level functions than + is currently necessary for universal functions. + +Casting from one dtype to another can be complex, and generally a casting +function may not implement all details of each input datatype (such as +non-native byte order or unaligned access). +Thus casting naturally is performed in up to three steps: + +1. The input datatype is normalized and prepared for the actual cast. +2. The cast is performed. +3. The cast result, which is in a normalized form, is cast to the requested + form (non-native byte order). + +although often only step 2. is required. + +Further, NumPy provides different casting kinds or safety specifiers: + +* "safe" +* "same_kind" +* "unsafe" + +and in some cases a cast may even be represented as a simple view. + + +**Motivation:** + +Similar to the common dtype/DType operation above, we again have two use cases: + +1. ``arr.astype(np.String)`` (current spelling ``arr.astype("S")``) +2. ``arr.astype(np.dtype("S8"))``. + +Where the first case is also noted in NEP 40 and 41 as a design goal, since +``np.String`` could also be an abstract DType as mentioned above. + +The implementation of casting should also come with as little duplicate +implementation as necessary, i.e. to avoid unnecessary methods on the +DTypes. +Furthermore, it is desirable that casting is implemented similar to universal +functions. + +Analogous to the above, the following also need to be defined: + +1. ``np.can_cast(dtype, DType, "safe")`` (instance to class) +2. ``np.can_cast(dtype, other_dtype, "safe")`` (casting an instance to another instance) + +overloading the meaning of ``dtype`` to mean either class or instance +(on the Python level). +The question of ``np.can_cast(DType, OtherDType, "safe")`` is also a +possibility and may be used internally. +However, it is initially not necessary to expose to Python. + + +**Implementation:** + +During DType creation, DTypes will have the ability to pass a list of +``CastingImpl`` objects, which can define casting to and from the DType. +One of these ``CastingImpl`` objects is special because it should define +the cast within the same DType (from one instance to another). +A DType which does not define this, must have only a single implementation +and not be parametric. + +Each ``CastingImpl`` has a specific DType signature: +``CastingImpl[InputDtype, RequestedDtype]``. +And implements the following methods and attributes: + +* ``adjust_descriptors(self, Tuple[DType] : input) -> casting, Tuple[DType]``. + Here ``casting`` signals the casting safeness (safe, unsafe, or same-kind) + and the output dtype tuple is used for more multi-step casting (see below). +* ``get_transferfunction(...) -> function handling cast`` (signature to be decided). + This function returns a low-level implementation of a strided casting function + ("transfer function"). +* ``cast_kind`` attribute with one of safe, unsafe, or same-kind. Used to + quickly decide casting safety when this is relevant. + +``adjust_descriptors`` provides information about whether or +not a cast is safe and is of importance mainly for parametric DTypes. +``get_transferfunction`` provides NumPy with a function capable of performing +the actual cast. Initially the implementation of ``get_transferfunction`` +will be *private*, and users will only be able to provide contiguous loops +with the signature. + +**Performing the Cast:** + +.. _cast_figure: +.. figure:: _static/casting_flow.svg + :figclass: align-center + +`The above figure <cast_figure>`_ illustrates the multi-step logic necessary to +cast for example an ``int24`` with a value of ``42`` to a string of length 20 +(``"S20"``). +In this example, the implementer only provided the functionality of casting +an ``int24`` to an ``S8`` string (which can hold all 24bit integers). +Due to this limited implementation, the full cast has to do multiple +conversions. The full process is: + +1. Call ``CastingImpl[Int24, String].adjust_descriptors((int24, "S20"))``. + This provides the information that ``CastingImpl[Int24, String]`` only + implements the cast of ``int24`` to ``"S8``. +2. Since ``"S8"`` does not match ``"S20"``, use + ``CastingImpl[String, String].get_transferfunction()`` + to find the transfer (casting) function to convert an ``"S8"`` into an ``"S20"`` +3. Fetch the transfer function to convert an ``int24`` to an ``"S8"`` using + ``CastingImpl[Int24, String].get_transferfunction()`` +4. Perform the actual cast using the two transfer functions: + ``int24(42) -> S8("42") -> S20("42")``. + +Note that in this example the ``adjust_descriptors`` function plays a less +central role. It becomes more important for ``np.can_cast``. + +Further, ``adjust_descriptors`` allows the implementation for +``np.array(42, dtype=int24).astype(String)`` to call +``CastingImpl[Int24, String].adjust_descriptors((int24, None))``. +In this case the result of ``(int24, "S8")`` defines the correct cast: +``np.array(42, dtype=int24),astype(String) == np.array("42", dtype="S8")``. + +**Casting Safety:** + +To answer the question of casting safety +``np.can_cast(int24, "S20", casting="safe")``, only the ``adjust_descriptors`` +function is required and called is in the same way as in +`the figure describing a cast <cast_figure>`_. +In this case, the calls to ``adjust_descriptors``, will also provide the +information that ``int24 -> "S8"`` as well as ``"S8" -> "S20"`` are safe casts, +and thus also the ``int24 -> "S20"`` is a safe cast. + +The casting safety can currently be "equivalent" when a cast is both safe +and can be performed using only a view. +The information that a cast is a simple "view" will instead be handled by +an additional flag. Thus the ``casting`` can have the 6 values in total: +safe, unsafe, same-kind as well as safe+view, unsafe+view, same-kind+view. +Where the current "equivalent" is the same as safe+view. + +(For more information on the ``adjust_descriptor`` signature see the +C-API section below.) + + +**Casting between instances of the same DType:** + +In general one of the casting implementations define by the DType implementor +must be ``CastingImpl[DType, DType]`` (unless there is only a singleton +instance). +To keep the casting to as few steps as possible, this implementation must +be capable any conversions between all instances of this DType. + + +**General Multi-Step Casting** + +In general we could implement certain casts, such as ``int8`` to ``int24`` +even if the user only provides an ``int16 -> int24`` cast. +This proposal currently does not provide this functionality. However, +it could be extended in the future to either find such casts dynamically, +or at least allow ``adjust_descriptors`` to return arbitray ``dtypes``. +If ``CastingImpl[Int8, Int24].adjust_descriptors((int8, int24))`` returns +``(int16, int24)``, the actual casting process could be extended to include +the ``int8 -> int16`` cast. Unlike the above example, which is limited +to at most three steps. + + +**Alternatives:** + +The choice of using only the DType classes in the first step of finding the +correct ``CastingImpl`` means that the default implementation of +``__common_dtype__`` has a reasonable definition of "safe casting" between +DTypes classes (although e.g. the concatenate operation using it may still +fail when attempting to find the actual common instance or cast). + +The split into multiple steps may seem to add complexity +rather than reduce it, however, it consolidates that we have the two distinct +signatures of ``np.can_cast(dtype, DTypeClass)`` and ``np.can_cast(dtype, other_dtype)``. +Further, the above API guarantees the separation of concerns for user DTypes. +The user ``Int24`` dtype does not have to handle all string lengths if it +does not wish to do so. Further, if an encoding was added to the ``String`` +DType, this does not affect the overall cast. +The ``adjust_descriptor`` function can keep returning the default encoding +and the ``CastingImpl[String, String]`` can take care of any necessary encoding +changes. + +The main alternative to the proposed design is to move most of the information +which is here pushed into the ``CastingImpl`` directly into methods +on the DTypes. This, however, will not allow the close similarity between casting +and universal functions. On the up side, it reduces the necessary indirection +as noted below. + +An initial proposal defined two methods ``__can_cast_to__(self, other)`` +to dynamically return ``CastingImpl``. +The advantage of this addition is that it removes the requirement to know all +possible casts at DType creation time (of one of the involved DTypes). +Such API could be added at a later time. It should be noted, however, +that it would be mainly useful for inheritance-like logic, which can be +problematic. As an example two different ``Float64WithUnit`` implementations +both could infer that they can unsafely cast between one another when in fact +some combinations should cast safely or preserve the Unit (both of which the +"base" ``Float64`` would discard). +In the proposed implementation this is not possible, since the two implementations +are not aware of each other. + + +**Notes:** + +The proposed ``CastingImpl`` is designed to be compatible with the +``UFuncImpl`` proposed in NEP 43. +While initially it will be a distinct object or C-struct, the aim is that +``CastingImpl`` can be a subclass or extension of ``UFuncImpl``. +Once this happens, this may naturally allow the use of a ``CastingImpl`` to +pass around a specialized casting function directly. + +In the future, we may consider adding a way to spell out that specific +casts are known to be *not* possible. + +In the above text ``CastingImpl`` is described as a Python object. In practice, +the current plan is to implement it as a C-side structure stored on the ``from`` +datatype. +A Python side API to get an equivalent ``CastingImpl`` object will be created, +but storing it (similar to the current implementation) on the ``from`` datatype +avoids the creation of cyclic reference counts. + +The way dispatching works for ``CastingImpl`` is planned to be limited initially +and fully opaque. +In the future, it may or may not be moved into a special UFunc, or behave +more like a universal function. + + +**Example:** + +The implementation for casting integers to datetime would currently generally +say that this cast is unsafe (it is always an unsafe cast). +Its ``adjust_descriptors`` functions may look like:: + + def adjust_descriptors(input): + from_dtype, to_dtype = input + + from_dtype = from_dtype.ensure_canonical() # ensure not byte-swapped + if to_dtype is None: + raise TypeError("Cannot convert to a NumPy datetime without a unit") + to_dtype = to_dtype.ensure_canonical() # ensure not byte-swapped + + # This is always an "unsafe" cast, but for int64, we can represent + # it by a simple view (if the dtypes are both canonical). + # (represented as C-side flags here). + safety_and_view = NPY_UNSAFE_CASTING | NPY_CAST_IS_VIEW + return safety_and_view, (from_dtype, to_dtype) + +.. note:: + + While NumPy currently defines some of these casts, with the possible + exception of the unit-less ``timedelta64`` it may be better to not + define these cast at all. In general we expect that user defined + DTypes will be using other methods such as ``unit.drop_unit(arr)`` + or ``arr * unit.seconds``. + + +C-Side API +^^^^^^^^^^ + +A Python side API shall not be defined here. This is a general side approach. + + +DType creation +"""""""""""""" + +As already mentioned in NEP 41, the interface to define new DTypes in C +is modeled after the limited API in Python: the above-mentioned slots +and some additional necessary information will thus be passed within a slots +struct and identified by ``ssize_t`` integers:: + + static struct PyArrayMethodDef slots[] = { + {NPY_dt_method, method_implementation}, + ..., + {0, NULL} + } + + typedef struct{ + PyTypeObject *typeobj; /* type of python scalar */ + int is_parametric; /* Is the dtype parametric? */ + int is_abstract; /* Is the dtype abstract? */ + int flags /* flags (to be discussed) */ + /* NULL terminated CastingImpl; is copied and references are stolen */ + CastingImpl *castingimpls[]; + PyType_Slot *slots; + PyTypeObject *baseclass; /* Baseclass or NULL */ + } PyArrayDTypeMeta_Spec; + + PyObject* PyArray_InitDTypeMetaFromSpec(PyArrayDTypeMeta_Spec *dtype_spec); + +all of this information will be copied during instantiation. + +**TODO:** The DType author should be able to at define new methods for +their DType, up to defining a full type object and in the future possibly even +extending the ``PyArrayDTypeMeta_Type`` struct. +We have to decide on how (and what) to make available to the user initially. +A proposed initial solution may be to simply allow inheriting from an existing +class. +Further this prevents overriding some slots, such as `==` which may not be +desirable. + + +The proposed method slots are (prepended with ``NPY_dt_``), these are +detailed above and given here for summary: + +* ``is_canonical(self) -> {0, 1}`` +* ``ensure_canonical(self) -> dtype`` +* ``default_descr(self) -> dtype`` (return must be native and should normally be a singleton) +* ``setitem(self, char *item_ptr, PyObject *value) -> {-1, 0}`` +* ``getitem(self, char *item_ptr, PyObject (base_obj) -> object or NULL`` +* ``discover_descr_from_pyobject(cls, PyObject) -> dtype or NULL`` +* ``common_dtype(cls, other) -> DType, NotImplemented, or NULL`` +* ``common_instance(self, other) -> dtype or NULL`` + +If not set, most slots are filled with slots which either error or defer automatically. +Non-parametric dtypes do not have to implement: + +* ``discover_descr_from_pyobject`` (uses ``default_descr`` instead) +* ``common_instance`` (uses ``default_descr`` instead) +* ``ensure_canonical`` (uses ``default_descr`` instead) + +Which will be correct for most dtypes *which do not store metadata*. + +Other slots may be replaced by convenience versions, e.g. sorting methods +can be defined by providing: + +* ``compare(self, char *item_ptr1, char *item_ptr2, int *res) -> {-1, 0}`` + *TODO: We would like an error return, is this reasonable? (similar to old + python compare)* + +which uses generic sorting functionality. In general, we could add a +functions such as: + +* ``get_sort_function(self, NPY_SORTKIND sort_kind) -> {out_sortfunction, NotImplemented, NULL}``. + If the sortkind is not understood it may be allowed to return ``NotImplemented``. + +in the future. However, for example sorting is likely better solved by the +implementation of multiple generalized ufuncs which are called internally. + +**Limitations:** + +Using the above ``PyArrayDTypeMeta_Spec`` struct, the structure itself can +only be extended clumsily (e.g. by adding a version tag to the ``slots`` +to indicate a new, longer version of the struct). +We could also provide the struct using a function, which however will require +memory management but would allow ABI-compatible extension +(the struct is freed again when the DType is created). + + +CastingImpl +""""""""""" + +The external API for ``CastingImpl`` will be limited initially to defining: + +* ``cast_kind`` attribute, which can be one of the supported casting kinds. + This is the safest cast possible. For example casting between two NumPy + strings is of course "safe" in general, but may be "same kind" in a specific + instance if the second string is shorter. If neither type is parametric the + ``adjust_descriptors`` must use it. +* ``adjust_descriptors(dtypes_in[2], dtypes_out[2], casting_out) -> int {0, -1}`` + The out dtypes must be set correctly to dtypes which the strided loop + (transfer function) can handle. Initially the result must have be instances + of the same DType class as the ``CastingImpl`` is defined for. + The ``casting_out`` will be set to ``NPY_SAFE_CASTING``, ``NPY_UNSAFE_CASTING``, + or ``NPY_SAME_KIND_CASTING``. With a new, additional, flag ``NPY_CAST_IS_VIEW`` + which can be set to indicate that no cast is necessary, but a simple view + is sufficient to perform the cast. + The cast should return ``-1`` when a custom error message is set and + ``NPY_NO_CASTING`` to indicate that a generic casting error should be + set (this is in most cases preferable). +* ``strided_loop(char **args, npy_intp *dimensions, npy_intp *strides, dtypes[2]) -> int {0, nonzero}`` (must currently succeed) + +This is identical to the proposed API for ufuncs. By default the two dtypes +are passed in as the last argument. On error return (if no error is set) a +generic error will be given. +More optimized loops are in use internally, and will be made available to users +in the future (see notes) +The iterator API does not currently support casting errors: this is +a bug that needs to be fixed. Until it is fixed the loop should always +succeed (return 0). + +Although verbose, the API shall mimic the one for creating a new DType. +The ``PyArrayCastingImpl_Spec`` will include a field for ``dtypes`` and +identical to a ``PyArrayUFuncImpl_Spec``:: + + typedef struct{ + int needs_api; /* whether the cast requires the API */ + PyArray_DTypeMeta *in_dtype; /* input DType class */ + PyArray_DTypeMeta *out_dtype; /* output DType class */ + /* NULL terminated slots defining the methods */ + PyType_Slot *slots; + } PyArrayUFuncImpl_Spec; + +The actual creation function ``PyArrayCastingImpl_FromSpec()`` will additionally +require a ``casting`` parameter to define the default (maximum) casting safety. +The internal representation of ufuncs and casting implementations may differ +initially if it makes implementation simpler, but should be kept opaque to +allow future merging. + +**TODO:** It may be possible to make this more close to the ufuncs or even +use a single FromSpec. This API shall only be finalized after/when NEP 43 +is finalized. + +**Notes:** + +We may initially allow users to define only a single loop. +However, internally NumPy optimizes far more, and this should be made +public incrementally, by either allowing to provide multiple versions, such +as: + +* contiguous inner loop +* strided inner loop +* scalar inner loop + +or more likely through an additional ``get_inner_loop`` function which has +additional information, such as the fixed strides (similar to our internal API). + +The above example does not yet include the definition of setup/teardown +functionality, which may overlap with ``get_inner_loop``. +Since these are similar to the UFunc machinery, this should be defined in +detail in NEP 43 and then incorporated identically into casting. + +Also the ``needs_api`` decision may actually be moved into a setup function, +and removed or mainly provided as a convenience flag. + +The slots/methods used will be prefixed ``NPY_uf_`` for similarity to the ufunc +machinery. + + + +Alternatives +"""""""""""" + +Aside from name changes, and possible signature tweaks, there seem to +be few alternatives to the above structure. +Keeping the creation process close the Python limited API has some advantage. +Convenience functions could still be provided to allow creation with less +code. +The central point in the above design is that the enumerated slots design +is extensible and can be changed without breaking binary compatibility. +A downside is the possible need to pass in e.g. integer flags using a void +pointer inside this structure. + +A downside of this is that compilers cannot warn about function +pointer incompatibilities. There is currently no proposed solution to this. + + +Issues +^^^^^^ + +Any possible design decision will have issues. + +The above split into Python objects has the disadvantage that reference cycles +naturally occur. For example a potential ``CastingImpl`` object needs to +hold on to both ``DTypes``. Further, a scalar type may want to own a +strong reference to the corresponding ``DType`` while the ``DType`` *must* +hold a strong reference to the scalar. +We do not believe that these reference cycles are an issue. The may +require implementation of of cyclic reference counting at some point, but +cyclic reference resolution is very common in Python and dtypes (especially +classes) are only a small number of objects. + +In some cases, the new split will add additional indirections to the code, +since methods on the DType have to be looked up and called. +This should not have serious performance impact and seems necessary to +achieve the desired flexibility. + +From a user-perspective, a more serious downside is that handling certain +functionality in the ``DType`` rather than directly can mean that error +messages need to be raised from places where less context is available. +This may mean that error messages can be less specific. +This will be alleviated by exception chaining. Also decisions such as +returning the casting safety (even when it is impossible to cast) allow +most exceptions to be set at a point where more context is available +and ensures uniform errors messages. + + +Implementation +-------------- + +Internally a few implementation details have to be decided. These will be +fully opaque to the user and can be changed at a later time. + +This includes: + +* How ``CastingImpl`` lookup, and thus the decision whether a cast is possible, + is defined. (This is speed relevant, although mainly during a transition + phase where UFuncs where NEP 43 is not yet implemented). + Thus, it is not very relevant to the NEP. It is only necessary to ensure fast + lookup during the transition phase for the current builtin Numerical types. + +* How the mapping from a python scalar (e.g. ``3.``) to the DType is + implemented. + +The main steps for implementation are outlined in :ref:`NEP 41 <NEP41>`. +This includes the internal restructure for how casting and array-coercion +works. +After this the new public API will be added incrementally. +This includes replacements for certain slots which are occasionally +directly used on the dtype (e.g. ``dtype->f->setitem``). + + +Discussion +---------- + +There is a large space of possible implementations with many discussions +in various places, as well as initial thoughts and design documents. +These are listed in the discussion of NEP 40 and not repeated here for +brevity. + + +References +---------- + +.. [1] NumPy currently inspects the value to allow the operations:: + + np.array([1], dtype=np.uint8) + 1 + np.array([1.2], dtype=np.float32) + 1. + + to return a ``uint8`` or ``float32`` array respectively. This is + further described in the documentation of `numpy.result_type`. + + +Copyright +--------- + +This document has been placed in the public domain. diff --git a/doc/neps/roadmap.rst b/doc/neps/roadmap.rst index 3780499a0..40fcbd325 100644 --- a/doc/neps/roadmap.rst +++ b/doc/neps/roadmap.rst @@ -6,6 +6,7 @@ This is a live snapshot of tasks and features we will be investing resources in. It may be used to encourage and inspire developers and to search for funding. + Interoperability ---------------- @@ -16,11 +17,21 @@ facilitate interoperability with all such packages, and the code that uses them, may include (among other things) interoperability protocols, better duck typing support and ndarray subclass handling. -- The ``__array_function__`` protocol is currently experimental and needs to be - matured. See :ref`NEP18` for details. -- New protocols for overriding other functionality in NumPy may be needed. -- Array duck typing, or handling "duck arrays", needs improvements. See - :ref:`NEP22` for details. +The ``__array_ufunc__`` and ``__array_function__`` protocols are stable, but +do not cover the whole API. New protocols for overriding other functionality +in NumPy are needed. Work in this area aims to bring to completion one or more +of the following proposals: + +- :ref:`NEP30` +- :ref:`NEP31` +- :ref:`NEP35` +- :ref:`NEP37` + +In addition we aim to provide ways to make it easier for other libraries to +implement a NumPy-compatible API. This may include defining consistent subsets +of the API, as discussed in `this section of NEP 37 +<https://numpy.org/neps/nep-0037-array-module.html#requesting-restricted-subsets-of-numpy-s-api>`__. + Extensibility ------------- @@ -42,56 +53,59 @@ improve the dtype system. - One of these should probably be the default for text data. The current behavior on Python 3 is neither efficient nor user friendly. -- ``np.dtype(int)`` should not be platform dependent -- Better coercion for string + number Performance ----------- -We want to further improve NumPy's performance, through: +Improvements to NumPy's performance are important to many users. The primary +topic at the moment is better use of SIMD instructions, also on platforms other +than x86 - see :ref:`NEP38`. + +Other performance improvement ideas include: -- Better use of SIMD instructions, also on platforms other than x86. -- Reducing ufunc overhead. +- Reducing ufunc and ``__array_function__`` overhead. - Optimizations in individual functions. +- A better story around parallel execution. Furthermore we would like to improve the benchmarking system, in terms of coverage, easy of use, and publication of the results (now `here <https://pv.github.io/numpy-bench>`__) as part of the docs or website. + Website and documentation ------------------------- -Our website (https://numpy.org) is in very poor shape and needs to be rewritten -completely. - -The NumPy `documentation <https://www.numpy.org/devdocs/user/index.html>`__ is -of varying quality - in particular the User Guide needs major improvements. +The NumPy `documentation <https://www.numpy.org/devdocs>`__ is of varying +quality. The API documentation is in good shape; tutorials and high-level +documentation on many topics are missing or outdated. See :ref:`NEP44` for +planned improvements. -Random number generation policy & rewrite ------------------------------------------ +Our website (https://numpy.org) was completely redesigned recently. We aim to +further improve it by adding translations, better Hugo-Sphinx integration via a +new Sphinx theme, and more (see `this tracking issue <https://github.com/numpy/numpy.org/issues/266>`__). -A new random number generation framework with higher performance generators is -close to completion, see :ref:`NEP19` and `PR 13163`_. -Indexing --------- +User experience +--------------- -We intend to add new indexing modes for "vectorized indexing" and "outer indexing", -see :ref:`NEP21`. +Type annotations +```````````````` +We aim to add type annotations for all NumPy functionality, so users can use +tools like `mypy`_ to type check their code and IDEs can improve their support +for NumPy. The existing type stubs in the `numpy-stubs`_ repository are being +improved and will be moved into the NumPy code base. -Continuous Integration ----------------------- +Platform support +```````````````` +We aim to increase our support for different hardware architectures. This +includes adding CI coverage when CI services are available, providing wheels on +PyPI for ARM64 (``aarch64``) and POWER8/9 (``ppc64le``), providing better +build and install documentation, and resolving build issues on other platforms +like AIX. -We depend on CI to discover problems as we continue to develop NumPy before the -code reaches downstream users. -- CI for more exotic platforms (if available as a service). -- Multi-package testing -- Add an official channel for numpy dev builds for CI usage by other projects so - they may confirm new builds do not break their package. - -Other functionality -------------------- +Maintenance +----------- - ``MaskedArray`` needs to be improved, ideas include: @@ -99,12 +113,17 @@ Other functionality - MaskedArray as a duck-array type, and/or - dtypes that support missing values -- A backend system for ``numpy.fft`` (so that e.g. ``fft-mkl`` doesn't need to monkeypatch numpy) -- Write a strategy on how to deal with overlap between NumPy and SciPy for ``linalg`` - and ``fft`` (and implement it). -- Deprecate ``np.matrix`` (very slowly) +- Fortran integration via ``numpy.f2py`` requires a number of improvements, see + `this tracking issue <https://github.com/numpy/numpy/issues/14938>`__. +- A backend system for ``numpy.fft`` (so that e.g. ``fft-mkl`` doesn't need to monkeypatch numpy). +- Write a strategy on how to deal with overlap between NumPy and SciPy for ``linalg``. +- Deprecate ``np.matrix`` (very slowly). +- Add new indexing modes for "vectorized indexing" and "outer indexing" (see :ref:`NEP21`). +- Make the polynomial API easier to use. +- Integrate an improved text file loader. +- Ufunc and gufunc improvements, see `gh-8892 <https://github.com/numpy/numpy/issues/8892>`__ + and `gh-11492 <https://github.com/numpy/numpy/issues/11492>`__. -.. _implementation: https://gist.github.com/shoyer/1f0a308a06cd96df20879a1ddb8f0006 -.. _`reference implementation`: https://github.com/bashtage/randomgen -.. _`PR 13163`: https://github.com/numpy/numpy/pull/13163 +.. _`mypy`: https://mypy.readthedocs.io +.. _`numpy-stubs`: https://github.com/numpy/numpy-stubs diff --git a/doc/release/upcoming_changes/13516.improvement.rst b/doc/release/upcoming_changes/13516.improvement.rst new file mode 100644 index 000000000..1b32b61d7 --- /dev/null +++ b/doc/release/upcoming_changes/13516.improvement.rst @@ -0,0 +1,47 @@ +Enable multi-platform SIMD compiler optimizations +------------------------------------------------- + +A series of improvements for NumPy infrastructure to pave the way to +**NEP-38**, that can be summarized as follow: + +- **New Build Arguments** : + + - ``--cpu-baseline`` to specify the minimal set of required + optimizations, default value is ``min`` which provides the minimum + CPU features that can safely run on a wide range of users + platforms. + + - ``--cpu-dispatch`` to specify the dispatched set of additional + optimizations, default value is ``max -xop -fma4`` which enables + all CPU features, except for AMD legacy features. + + - ``--disable-optimization`` to explicitly disable the whole new + improvements, It also adds a new **C** compiler #definition + called ``NPY_DISABLE_OPTIMIZATION`` which it can be used as + guard for any SIMD code. + +- **Advanced CPU dispatcher**: A flexible cross-architecture CPU dispatcher built + on the top of Python/Numpy distutils, support all common compilers with a wide range of CPU features. + + The new dispatcher requires a special file extension ``*.dispatch.c`` to mark the dispatch-able + **C** sources. These sources have the ability to be compiled multiple times so that each compilation process + represents certain CPU features and provides different #definitions and flags that affect the code paths. + +- **New auto-generated C header ``core/src/common/_cpu_dispatch.h``** + This header is generated by the distutils module 'ccompiler_opt', and contains all the #definitions + and headers of instruction sets, that had been configured through command arguments '--cpu-baseline' and '--cpu-dispatch'. + +- **New C header ``core/src/common/npy_cpu_dispatch.h``** + + This header contains all utilities that required for the whole CPU dispatching process, + it also can be considered as a bridge linking the new infrastructure work with NumPy CPU runtime detection. + +- **Add new attributes to NumPy umath module(Python level)** + + - ``__cpu_baseline__`` a list contains the minimal set of required optimizations that supported + by the compiler and platform according to the specified values to command argument '--cpu-baseline'. + + - ``__cpu_dispatch__`` a list contains the dispatched set of additional optimizations that supported by the compiler + and platform according to the specified values to command argument '--cpu-dispatch'. + +- **Print the supported CPU features during the run of PytestTester** diff --git a/doc/release/upcoming_changes/14882.deprecation.rst b/doc/release/upcoming_changes/14882.deprecation.rst new file mode 100644 index 000000000..db3b39d4c --- /dev/null +++ b/doc/release/upcoming_changes/14882.deprecation.rst @@ -0,0 +1,30 @@ +Using the aliases of builtin types like ``np.int`` is deprecated +---------------------------------------------------------------- + +For a long time, ``np.int`` has been an alias of the builtin ``int``. This is +repeatedly a cause of confusion for newcomers, and is also simply not useful. + +These aliases have been deprecated. The table below shows the full list of +deprecated aliases, along with their exact meaning. Replacing uses of items in +the first column with the contents of the second column will work identically +and silence the deprecation warning. + +In many cases, it may have been intended to use the types from the third column. +Be aware that use of these types may result in subtle but desirable behavior +changes. + +================== ================================= ================================================================== +Deprecated name Identical to Possibly intended numpy type +================== ================================= ================================================================== +``numpy.bool`` ``bool`` `numpy.bool_` +``numpy.int`` ``int`` `numpy.int_` (default int dtype), `numpy.cint` (C ``int``) +``numpy.float`` ``float`` `numpy.float_`, `numpy.double` (equivalent) +``numpy.complex`` ``complex`` `numpy.complex_`, `numpy.cdouble` (equivalent) +``numpy.object`` ``object`` `numpy.object_` +``numpy.str`` ``str`` `numpy.str_` +``numpy.long`` ``int`` (``long`` on Python 2) `numpy.int_` (C ``long``), `numpy.longlong` (largest integer type) +``numpy.unicode`` ``str`` (``unicode`` on Python 2) `numpy.unicode_` +================== ================================= ================================================================== + +Note that for technical reasons these deprecation warnings will only be emitted +on Python 3.7 and above. diff --git a/doc/release/upcoming_changes/16056.deprecation.rst b/doc/release/upcoming_changes/16056.deprecation.rst new file mode 100644 index 000000000..788b8c30a --- /dev/null +++ b/doc/release/upcoming_changes/16056.deprecation.rst @@ -0,0 +1,13 @@ +Inexact matches for mode and searchside are deprecated +------------------------------------------------------ +Inexact and case insensitive matches for mode and searchside were +valid inputs earlier and will give a DeprecationWarning now. +For example, below are some example usages which are now deprecated and will +give a DeprecationWarning. + + import numpy as np + arr = np.array([[3, 6, 6], [4, 5, 1]]) + # mode: inexact match + np.ravel_multi_index(arr, (7, 6), mode="clap") # should be "clip" + # searchside: inexact match + np.searchsorted(arr[0], 4, side='random') # should be "right" diff --git a/doc/release/upcoming_changes/16200.compatibility.rst b/doc/release/upcoming_changes/16200.compatibility.rst new file mode 100644 index 000000000..d0fd51265 --- /dev/null +++ b/doc/release/upcoming_changes/16200.compatibility.rst @@ -0,0 +1,64 @@ +NumPy Scalars are cast when assigned to arrays +---------------------------------------------- + +When creating or assigning to arrays, in all relevant cases NumPy +scalars will now be cast identically to NumPy arrays. In particular +this changes the behaviour in some cases which previously raised an +error:: + + np.array([np.float64(np.nan)], dtype=np.int64) + +will succeed at this time (this may change) and return an undefined result +(usually the smallest possible integer). This also affects assignments:: + + arr[0] = np.float64(np.nan) + +Note, this already happened for ``np.array(np.float64(np.nan), dtype=np.int64)`` +and that the behaviour is unchanged for ``np.nan`` itself which is a Python +float. +To avoid backward compatibility issues, at this time assignment from +``datetime64`` scalar to strings of too short length remains supported. +This means that ``np.asarray(np.datetime64("2020-10-10"), dtype="S5")`` +succeeds now, when it failed before. In the long term this may be +deprecated or the unsafe cast may be allowed generally to make assignment +of arrays and scalars behave consistently. + + +Array coercion changes when Strings and other types are mixed +------------------------------------------------------------- + +When stringss and other types are mixed, such as:: + + np.array(["string", np.float64(3.)], dtype="S") + +The results will change, which may lead to string dtypes with longer strings +in some cases. In particularly, if ``dtype="S"`` is not provided any numerical +value will lead to a string results long enough to hold all possible numerical +values. (e.g. "S32" for floats). Note that you should always provide +``dtype="S"`` when converting non-strings to strings. + +If ``dtype="S"`` is provided the results will be largely identical to before, +but NumPy scalars (not a Python float like ``1.0``), will still enforce +a uniform string length:: + + np.array([np.float64(3.)], dtype="S") # gives "S32" + np.array([3.0], dtype="S") # gives "S3" + +while previously the first version gave the same result as the second. + + +Array coercion restructure +-------------------------- + +Array coercion has been restructured. In general, this should not affect +users. In extremely rare corner cases where array-likes are nested:: + + np.array([array_like1]) + +things will now be more consistent with:: + + np.array([np.array(array_like1)]) + +which could potentially change output subtly for badly defined array-likes. +We are not aware of any such case where the results were not clearly +incorrect previously. diff --git a/doc/release/upcoming_changes/16232.deprecation.rst b/doc/release/upcoming_changes/16232.deprecation.rst new file mode 100644 index 000000000..d1ac7f044 --- /dev/null +++ b/doc/release/upcoming_changes/16232.deprecation.rst @@ -0,0 +1,6 @@ +``outer`` and ``ufunc.outer`` deprecated for matrix +--------------------------------------------------- +``np.matrix`` use with `~numpy.outer` or generic ufunc outer +calls such as ``numpy.add.outer``. Previously, matrix was +converted to an array here. This will not be done in the future +requiring a manual conversion to arrays. diff --git a/doc/release/upcoming_changes/16476.new_feature.rst b/doc/release/upcoming_changes/16476.new_feature.rst new file mode 100644 index 000000000..acfe0bd72 --- /dev/null +++ b/doc/release/upcoming_changes/16476.new_feature.rst @@ -0,0 +1,9 @@ +``norm=backward``, ``forward`` keyword options for ``numpy.fft`` functions +-------------------------------------------------------------------------- +The keyword argument option ``norm=backward`` is added as an alias for ``None`` +and acts as the default option; using it has the direct transforms unscaled +and the inverse transforms scaled by ``1/n``. + +Using the new keyword argument option ``norm=forward`` has the direct +transforms scaled by ``1/n`` and the inverse transforms unscaled (i.e. exactly +opposite to the default option ``norm=backward``). diff --git a/doc/release/upcoming_changes/16515.new_feature.rst b/doc/release/upcoming_changes/16515.new_feature.rst new file mode 100644 index 000000000..5b3803429 --- /dev/null +++ b/doc/release/upcoming_changes/16515.new_feature.rst @@ -0,0 +1,8 @@ +NumPy is now typed +------------------ +Type annotations have been added for large parts of NumPy. There is +also a new `numpy.typing` module that contains useful types for +end-users. The currently available types are + +- ``ArrayLike``: for objects that can be coerced to an array +- ``DtypeLike``: for objects that can be coerced to a dtype diff --git a/doc/release/upcoming_changes/16519.improvement.rst b/doc/release/upcoming_changes/16519.improvement.rst new file mode 100644 index 000000000..50c4305e5 --- /dev/null +++ b/doc/release/upcoming_changes/16519.improvement.rst @@ -0,0 +1,4 @@ +Thread-safe f2py callback functions +----------------------------------- + +Callback functions in f2py are now threadsafe. diff --git a/doc/release/upcoming_changes/16554.compatibility.rst b/doc/release/upcoming_changes/16554.compatibility.rst new file mode 100644 index 000000000..a9f3f07e0 --- /dev/null +++ b/doc/release/upcoming_changes/16554.compatibility.rst @@ -0,0 +1,10 @@ +Numeric-style type names have been removed from type dictionaries +----------------------------------------------------------------- + +To stay in sync with the deprecation for ``np.dtype("Complex64")`` +and other numeric-style (capital case) types. These were removed +from ``np.sctypeDict`` and ``np.typeDict``. You should use +the lower case versions instead. Note that ``"Complex64"`` +corresponds to ``"complex128"`` and ``"Complex32"`` corresponds +to ``"complex64"``. The numpy style (new) versions, denote the full +size and not the size of the real/imaginary part. diff --git a/doc/release/upcoming_changes/16554.deprecation.rst b/doc/release/upcoming_changes/16554.deprecation.rst new file mode 100644 index 000000000..5602d2d53 --- /dev/null +++ b/doc/release/upcoming_changes/16554.deprecation.rst @@ -0,0 +1,8 @@ +Further Numeric Style types Deprecated +-------------------------------------- + +The remaining numeric-style type codes ``Bytes0``, ``Str0``, +``Uint32``, ``Uint64``, and ``Datetime64`` +have been deprecated. The lower-case variants should be used +instead. For bytes and string ``"S"`` and ``"U"`` +are further alternatives. diff --git a/doc/release/upcoming_changes/16554.expired.rst b/doc/release/upcoming_changes/16554.expired.rst new file mode 100644 index 000000000..9b5718f8a --- /dev/null +++ b/doc/release/upcoming_changes/16554.expired.rst @@ -0,0 +1,5 @@ +* The deprecation of numeric style type-codes ``np.dtype("Complex64")`` + (with upper case spelling), is expired. ``"Complex64"`` corresponded to + ``"complex128"`` and ``"Complex32"`` corresponded to ``"complex64"``. +* The deprecation of ``np.sctypeNA`` and ``np.typeNA`` is expired. Both + have been removed from the public API. Use ``np.typeDict`` instead. diff --git a/doc/release/upcoming_changes/16558.new_feature.rst b/doc/release/upcoming_changes/16558.new_feature.rst new file mode 100644 index 000000000..9bd508e83 --- /dev/null +++ b/doc/release/upcoming_changes/16558.new_feature.rst @@ -0,0 +1,9 @@ +``numpy.typing`` is accessible at runtime +----------------------------------------- +The types in ``numpy.typing`` can now be imported at runtime. Code +like the following will now work: + +.. code:: python + + from numpy.typing import ArrayLike + x: ArrayLike = [1, 2, 3, 4] diff --git a/doc/release/upcoming_changes/16589.compatibility.rst b/doc/release/upcoming_changes/16589.compatibility.rst new file mode 100644 index 000000000..d65fb43f6 --- /dev/null +++ b/doc/release/upcoming_changes/16589.compatibility.rst @@ -0,0 +1,8 @@ +``nickname`` attribute removed from ABCPolyBase +----------------------------------------------- + +An abstract property ``nickname`` has been removed from ``ABCPolyBase`` as it +was no longer used in the derived convenience classes. +This may affect users who have derived classes from ``ABCPolyBase`` and +overridden the methods for representation and display, e.g. ``__str__``, +``__repr__``, ``_repr_latex``, etc. diff --git a/doc/release/upcoming_changes/16592.compatibility.rst b/doc/release/upcoming_changes/16592.compatibility.rst new file mode 100644 index 000000000..289e768fc --- /dev/null +++ b/doc/release/upcoming_changes/16592.compatibility.rst @@ -0,0 +1,13 @@ +float->timedelta and uint64->timedelta promotion will raise a TypeError +----------------------------------------------------------------------- +Float and timedelta promotion consistently raises a TypeError. +``np.promote_types("float32", "m8")`` aligns with +``np.promote_types("m8", "float32")`` now and both raise a TypeError. +Previously, ``np.promote_types("float32", "m8")`` returned ``"m8"`` which +was considered a bug. + +Uint64 and timedelta promotion consistently raises a TypeError. +``np.promote_types("uint64", "m8")`` aligns with +``np.promote_types("m8", "uint64")`` now and both raise a TypeError. +Previously, ``np.promote_types("uint64", "m8")`` returned ``"m8"`` which +was considered a bug. diff --git a/doc/source/conf.py b/doc/source/conf.py index 65dcae245..b908a5a28 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -88,7 +88,7 @@ pygments_style = 'sphinx' def setup(app): # add a config value for `ifconfig` directives app.add_config_value('python_version_major', str(sys.version_info.major), 'env') - app.add_lexer('NumPyC', NumPyLexer(stripnl=False)) + app.add_lexer('NumPyC', NumPyLexer) # ----------------------------------------------------------------------------- # HTML output diff --git a/doc/source/dev/gitwash/development_setup.rst b/doc/source/dev/gitwash/development_setup.rst index 9027dda64..a7e9c28b9 100644 --- a/doc/source/dev/gitwash/development_setup.rst +++ b/doc/source/dev/gitwash/development_setup.rst @@ -1,145 +1,178 @@ -==================================== -Getting started with Git development -==================================== +.. _development-setup: -This section and the next describe in detail how to set up git for working -with the NumPy source code. If you have git already set up, skip to -:ref:`development-workflow`. +############################################################################## +Setting up git for NumPy development +############################################################################## -Basic Git setup -############### +To contribute code or documentation, you first need -* :ref:`install-git`. -* Introduce yourself to Git:: +#. git installed on your machine +#. a GitHub account +#. a fork of NumPy - git config --global user.email you@yourdomain.example.com - git config --global user.name "Your Name Comes Here" -.. _forking: +****************************************************************************** +Install git +****************************************************************************** + +You may already have git; check by typing ``git --version``. If it's +installed you'll see some variation of ``git version 2.11.0``. +If instead you see ``command is not recognized``, ``command not +found``, etc., +`install git <https://git-scm.com/book/en/v2/Getting-Started-Installing-Git>`_. -Making your own copy (fork) of NumPy -#################################### +Then set your name and email: :: -You need to do this only once. The instructions here are very similar -to the instructions at http://help.github.com/forking/ - please see that -page for more detail. We're repeating some of it here just to give the -specifics for the NumPy_ project, and to suggest some default names. + git config --global user.email you@yourdomain.example.com + git config --global user.name "Your Name" .. _set-up-and-configure-a-github-account: -Set up and configure a github_ account -====================================== +****************************************************************************** +Create a GitHub account +****************************************************************************** -If you don't have a github_ account, go to the github_ page, and make one. +If you don't have a GitHub account, visit https://github.com/join to create +one. -You then need to configure your account to allow write access - see the -``Generating SSH keys`` help on `github help`_. +.. _forking: -Create your own forked copy of NumPy_ -========================================= +****************************************************************************** +Create a NumPy fork +****************************************************************************** -#. Log into your github_ account. -#. Go to the NumPy_ github home at `NumPy github`_. -#. Click on the *fork* button: +``Forking`` has two steps -- visit GitHub to create a fork repo in your +account, then make a copy of it on your own machine. - .. image:: forking_button.png +Create the fork repo +============================================================================== - After a short pause, you should find yourself at the home page for - your own forked copy of NumPy_. +#. Log into your GitHub account. +#. Go to the `NumPy GitHub home <https://github.com/numpy/numpy>`_. +#. At the upper right of the page, click ``Fork``: -.. include:: git_links.inc + .. image:: forking_button.png + You'll see -.. _set-up-fork: + .. image:: forking_message.png -Set up your fork -################ + and then you'll be taken to the home page of your forked copy: -First you follow the instructions for :ref:`forking`. + .. image:: forked_page.png -Overview -======== -:: +.. _set-up-fork: - git clone https://github.com/your-user-name/numpy.git - cd numpy - git remote add upstream https://github.com/numpy/numpy.git +Make the local copy +============================================================================== -In detail -========= +#. In the directory where you want the copy created, run :: -Clone your fork ---------------- + git clone https://github.com/your-user-name/numpy.git -#. Clone your fork to the local computer with ``git clone - https://github.com/your-user-name/numpy.git`` -#. Investigate. Change directory to your new repo: ``cd numpy``. Then - ``git branch -a`` to show you all branches. You'll get something - like:: + You'll see something like: :: - * master - remotes/origin/master + $ git clone https://github.com/your-user-name/numpy.git + Cloning into 'numpy'... + remote: Enumerating objects: 12, done. + remote: Counting objects: 100% (12/12), done. + remote: Compressing objects: 100% (12/12), done. + remote: Total 175837 (delta 0), reused 0 (delta 0), pack-reused 175825 + Receiving objects: 100% (175837/175837), 78.16 MiB | 9.87 MiB/s, done. + Resolving deltas: 100% (139317/139317), done. - This tells you that you are currently on the ``master`` branch, and - that you also have a ``remote`` connection to ``origin/master``. - What remote repository is ``remote/origin``? Try ``git remote -v`` to - see the URLs for the remote. They will point to your github_ fork. + A directory ``numpy`` is created on your machine. (If you already have + a numpy directory, GitHub will choose a different name like ``numpy-1``.) + :: - Now you want to connect to the upstream `NumPy github`_ repository, so - you can merge in changes from trunk. + $ ls -l + total 0 + drwxrwxrwx 1 bjn bjn 4096 Jun 20 07:20 numpy .. _linking-to-upstream: -Linking your repository to the upstream repo --------------------------------------------- +#. Give the name ``upstream`` to the main NumPy repo: :: + + cd numpy + git remote add upstream https://github.com/numpy/numpy.git + +#. Set up your repository so ``git pull`` pulls from ``upstream`` by + default: :: + + git config branch.master.remote upstream + git config branch.master.merge refs/heads/master -:: +****************************************************************************** +Look it over +****************************************************************************** - cd numpy - git remote add upstream https://github.com/numpy/numpy.git +#. The branches shown by ``git branch -a`` will include -``upstream`` here is just the arbitrary name we're using to refer to the -main NumPy_ repository at `NumPy github`_. + - the ``master`` branch you just cloned on your own machine + - the ``master`` branch from your fork on GitHub, which git named + ``origin`` by default + - the ``master`` branch on the the main NumPy repo, which you named + ``upstream``. -Just for your own satisfaction, show yourself that you now have a new -'remote', with ``git remote -v show``, giving you something like:: + :: - upstream https://github.com/numpy/numpy.git (fetch) - upstream https://github.com/numpy/numpy.git (push) - origin https://github.com/your-user-name/numpy.git (fetch) - origin https://github.com/your-user-name/numpy.git (push) + master + remotes/origin/master + remotes/upstream/master -To keep in sync with changes in NumPy, you want to set up your repository -so it pulls from ``upstream`` by default. This can be done with:: + If ``upstream`` isn't there, it will be added after you access the + NumPy repo with a command like ``git fetch`` or ``git pull``. - git config branch.master.remote upstream - git config branch.master.merge refs/heads/master -You may also want to have easy access to all pull requests sent to the -NumPy repository:: +#. The repos shown by ``git remote -v show`` will include your fork on GitHub + and the main repo: :: - git config --add remote.upstream.fetch '+refs/pull/*/head:refs/remotes/upstream/pr/*' + upstream https://github.com/numpy/numpy.git (fetch) + upstream https://github.com/numpy/numpy.git (push) + origin https://github.com/your-user-name/numpy.git (fetch) + origin https://github.com/your-user-name/numpy.git (push) -Your config file should now look something like (from -``$ cat .git/config``):: +#. ``git config --list`` will include :: - [core] - repositoryformatversion = 0 - filemode = true - bare = false - logallrefupdates = true - ignorecase = true - precomposeunicode = false - [remote "origin"] - url = https://github.com/your-user-name/numpy.git - fetch = +refs/heads/*:refs/remotes/origin/* - [remote "upstream"] - url = https://github.com/numpy/numpy.git - fetch = +refs/heads/*:refs/remotes/upstream/* - fetch = +refs/pull/*/head:refs/remotes/upstream/pr/* - [branch "master"] - remote = upstream - merge = refs/heads/master + user.email=your_email@example.com + user.name=Your Name + remote.origin.url=git@github.com:your-github-id/numpy.git + remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* + branch.master.remote=upstream + branch.master.merge=refs/heads/master + remote.upstream.url=https://github.com/numpy/numpy.git + remote.upstream.fetch=+refs/heads/*:refs/remotes/upstream/* .. include:: git_links.inc + + +****************************************************************************** +Optional: set up SSH keys to avoid passwords +****************************************************************************** + +Cloning your NumPy fork repo required no password, because it read the remote +repo without changing it. Later, though, submitting your pull requests will +write to it, and GitHub will ask for your username and password -- even though +it's your own repo. You can eliminate this authentication without compromising +security by `setting up SSH keys \ +<https://help.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh>`_. + +**If you set up the keys before cloning**, the instructions above change +slightly. Instead of :: + + git clone https://github.com/your-user-name/numpy.git + +run :: + + git clone git@github.com:numpy/numpy.git + +and instead of showing an ``https`` URL, ``git remote -v`` will show :: + + origin git@github.com:your-user-name/numpy.git (fetch) + origin git@github.com:your-user-name/numpy.git (push) + + +**If you have cloned already** and want to start using SSH, see +`Switching remote URLs from HTTPS to SSH \ +<https://help.github.com/en/github/using-git/changing-a-remotes-url#switching-remote-urls-from-https-to-ssh>`_. diff --git a/doc/source/dev/gitwash/forked_page.png b/doc/source/dev/gitwash/forked_page.png Binary files differnew file mode 100644 index 000000000..f369cab3a --- /dev/null +++ b/doc/source/dev/gitwash/forked_page.png diff --git a/doc/source/dev/gitwash/forking_button.png b/doc/source/dev/gitwash/forking_button.png Binary files differindex d0e04134d..9750c0947 100644..100755 --- a/doc/source/dev/gitwash/forking_button.png +++ b/doc/source/dev/gitwash/forking_button.png diff --git a/doc/source/dev/gitwash/forking_message.png b/doc/source/dev/gitwash/forking_message.png Binary files differnew file mode 100644 index 000000000..631296017 --- /dev/null +++ b/doc/source/dev/gitwash/forking_message.png diff --git a/doc/source/dev/governance/people.rst b/doc/source/dev/governance/people.rst index db2704d09..18366402e 100644 --- a/doc/source/dev/governance/people.rst +++ b/doc/source/dev/governance/people.rst @@ -12,31 +12,34 @@ Steering council * Ralf Gommers +* Allan Haldane + * Charles Harris +* Stephan Hoyer + +* Matti Picus + * Nathaniel Smith * Julian Taylor * Pauli Virtanen -* Eric Wieser - -* Marten van Kerkwijk - -* Stephan Hoyer +* Stéfan van der Walt -* Allan Haldane +* Eric Wieser -* Stefan van der Walt Emeritus members ---------------- -* Travis Oliphant - Project Founder / Emeritus Leader (served: 2005-2012) +* Travis Oliphant -- project founder / emeritus leader (2005-2012) + +* Alex Griffing (2015-2017) -* Alex Griffing (served: 2015-2017) +* Marten van Kerkwijk (2017-2019) NumFOCUS Subcommittee @@ -56,7 +59,7 @@ NumFOCUS Subcommittee Institutional Partners ---------------------- -* UC Berkeley (Stefan van der Walt, Sebastian Berg, Warren Weckesser, Ross Barnowski) +* UC Berkeley (Stéfan van der Walt, Sebastian Berg, Warren Weckesser, Ross Barnowski) * Quansight (Ralf Gommers, Hameer Abbasi, Melissa Weber Mendonça, Mars Lee, Matti Picus) diff --git a/doc/source/dev/howto-docs.rst b/doc/source/dev/howto-docs.rst index e8bafd254..17194bd58 100644 --- a/doc/source/dev/howto-docs.rst +++ b/doc/source/dev/howto-docs.rst @@ -4,146 +4,153 @@ How to contribute to the NumPy documentation ############################################ -The *Documentation* for a software project is the set of reference, -instructional, educational, informative material generated by the project -developers and contributors, as well as discussions, presentations, videos and -other user-generated content. It may include learning-oriented content (such as -tutorials and how-tos), use-cases or in-depth explanations and reference for -developers. - -If you're reading this page, you probably want to help. This guide is meant to -help you decide which kind of content you'll write, as well as give you some -tips and instructions for submitting it to the official NumPy documentation -(that is, the documentation that ships with NumPy and lives on the -:ref:`official project pages <numpy_docs_mainpage>`). Keep in mind that if you -don't want to do this, writing a tutorial on your own blog, creating a YouTube -video or answering questions on social media or Stack Overflow are also great -contributions! - -NumPy has a Documentation Team. We have open meetings on Zoom every three weeks -and invite everyone to join. Don't hesitate to reach out if you have questions -or just need someone to guide you through your first steps - we're always happy -to help. Meetings are usually announced on the `numpy-discussion mailing list -<https://mail.python.org/mailman/listinfo/numpy-discussion>`__. Meeting minutes -are taken `on hackmd.io <https://hackmd.io/oB_boakvRqKR-_2jRV-Qjg>`__ and stored -in the `NumPy Archive repository <https://github.com/numpy/archive>`__. - -You can find larger planned and in-progress documentation improvement ideas `at +This guide will help you decide what to contribute and how to submit it to the +official NumPy documentation. + +****************************************************************************** +Documentation team meetings +****************************************************************************** + +The NumPy community has set a firm goal of improving its documentation. We +hold regular documentation meetings on Zoom (dates are announced on the +`numpy-discussion mailing list +<https://mail.python.org/mailman/listinfo/numpy-discussion>`__), and everyone +is welcome. Reach out if you have questions or need +someone to guide you through your first steps -- we're happy to help. +Minutes are taken `on hackmd.io <https://hackmd.io/oB_boakvRqKR-_2jRV-Qjg>`__ +and stored in the `NumPy Archive repository +<https://github.com/numpy/archive>`__. + +************************* +What's needed +************************* +NumPy docs have the details covered. API reference +documentation is generated directly from +`docstrings <https://www.python.org/dev/peps/pep-0257/>`_ in the code +when the documentation is :ref:`built<howto-build-docs>`. + +What we lack are docs with broader scope -- tutorials, how-tos, and explanations. +Reporting defects is another way to contribute. We discuss both. + +************************* +Contributing fixes +************************* + +We're eager to hear about and fix doc defects. But to attack the biggest +problems we end up having to defer or overlook some bug reports. Here are the +best defects to go after. + +Top priority goes to **technical inaccuracies** -- a docstring missing a +parameter, a faulty description of a function/parameter/method, and so on. +Other "structural" defects like broken links also get priority. All these fixes +are easy to confirm and put in place. You can submit +a `pull request (PR) <https://numpy.org/devdocs/dev/index.html#devindex>`__ +with the fix, if you know how to do that; otherwise please `open an issue +<https://github.com/numpy/numpy/issues>`__. + +**Typos and misspellings** fall on a lower rung; we welcome hearing about them but +may not be able to fix them promptly. These too can be handled as pull +requests or issues. + +Obvious **wording** mistakes (like leaving out a "not") fall into the typo +category, but other rewordings -- even for grammar -- require a judgment call, +which raises the bar. Test the waters by first presenting the fix as an issue. + +****************************************************************************** +Contributing new pages +****************************************************************************** + +Your frustrations using our documents are our best guide to what needs fixing. + +If you write a missing doc you join the front line of open source, but it's +a meaningful contribution just to let us know what's missing. If you want to +compose a doc, run your thoughts by the `mailing list +<https://mail.python.org/mailman/listinfo/numpy-discussion>`__ for futher +ideas and feedback. If you want to alert us to a gap, +`open an issue <https://github.com/numpy/numpy/issues>`__. See +`this issue <https://github.com/numpy/numpy/issues/15760>`__ for an example. + +If you're looking for subjects, our formal roadmap for documentation is a +*NumPy Enhancement Proposal (NEP)*, +`NEP 44 - Restructuring the NumPy Documentation <https://www.numpy.org/neps/nep-0044-restructuring-numpy-docs>`__. +It identifies areas where our docs need help and lists several +additions we'd like to see, including Jupyter notebooks. + +You can find larger planned and in-progress ideas `at our GitHub project <https://github.com/orgs/numpy/projects/2>`__. -Current vision for the documentation: NEP 44 --------------------------------------------- +.. _tutorials_howtos_explanations: -Recently, the NumPy community approved a *NumPy Enhancement Proposal (NEP)* -about documentation, -:ref:`NEP 44 - Restructuring the NumPy Documentation <NEP44>`. -**Where is the documentation?** +Formula writing +============================================================================== +There are formulas for writing useful documents, and four formulas +cover nearly everything. There are four formulas because there are four +categories of document -- ``tutorial``, ``how-to guide``, ``explanation``, +and ``reference``. The insight that docs divide up this way belongs to +Daniele Procida, who goes on +`in this short article <https://documentation.divio.com/>`__ to explain +the differences and reveal the formulas. When you begin a document or +propose one, have in mind which of these types it will be. -The main page for the :ref:`NumPy Documentation <numpy_docs_mainpage>` lists -several categories. The documents mentioned there live in different places. -- **Tutorials, How-Tos, Explanations:** These documents are stored in the NumPy - source code tree, which means that in order to add them to the official - documentation, you have to download the NumPy source code, - :ref:`build it <howto-build-docs>` and submit your changes via a - :ref:`GitHub pull request <devindex>`. +.. _contributing: -- **API Reference:** These are mainly the result of rendering the NumPy code - `docstrings <https://www.python.org/dev/peps/pep-0257/>`__ into formatted - documents. They are automatically generated when the NumPy documentation is - :ref:`built from source<howto-build-docs>`. -**Datasets** +More on contributing +============================================================================== -If you are writing a tutorial or how-to, we encourage you to use real images and -data (provided they are appropriately licensed and available). This makes the -material more engaging for readers, and choosing the right data can add -pedagogical value to your content. +Don't worry if English is not your first language, or if you can only come up +with a rough draft. Open source is a community effort. Do your best -- we'll +help fix issues. -*Note: currently we cannot easily use data in other packages (except, e.g., from -SciPy or Matplotlib). We plan to create a dedicated datasets package, but that's -not ready yet - please discuss with us if you have data sources in mind.* +Images and real-life data make text more engaging and powerful, but be sure +what you use is appropriately licensed and available. Here again, even a rough +idea for artwork can be polished by others. -Creating new content --------------------- +For now, the only data formats accepted by NumPy are those also used by other +Python scientific libraries like pandas, SciPy, or Matplotlib. We're +developing a package to accept more formats; contact us for details. -The documentation is written in restructuredText, which is the format required -by Sphinx, the tool most Python projects use to automatically build and link the -documentation within the project. You can read the -`Quick reStructuredText Guide +NumPy documentation is kept in the source code tree. To get your document +into the docbase you must download the tree, :ref:`build it +<howto-build-docs>`, and submit a pull request. If GitHub and pull requests +are new to you, check our :ref:`Contributor Guide <devindex>`. + +Our markup language is reStructuredText (rST), which is more elaborate than +Markdown. Sphinx, the tool many Python projects use to build and link project +documentation, converts the rST into HTML and other formats. For more on +rST, see the `Quick reStructuredText Guide <https://docutils.sourceforge.io/docs/user/rst/quickref.html>`__ or the `reStructuredText Primer -<http://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html>`__ for -more information. - -If you have already decided which type of document you want to write, you can -check out the following specific guides: - -- Guide to writing Tutorials (TODO) -- :ref:`Guide to writing reference (API) documentation: the numpydoc docstring - guide <howto-document>` - -Major additions to the documentation (e.g. new tutorials) should be proposed to -the `mailing list -<https://mail.python.org/mailman/listinfo/numpy-discussion>`__. - -Other ways to contribute ------------------------- - -Correcting technical inaccuracies in the documentation are high priority. For -example, if a docstring is missing a parameter or the description of a -fuction/parameter/method etc. is incorrect. Other "structural" defects like -broken links are also high priority. - -Proposals for changes that improve the clarity of the documentation are welcome. -However, "clarity" is a bit subjective, so such proposals are best made by -raising issues that describe what could be improved in the current -documentation. Proposals that include specific suggestions for the improvement -are encouraged as the proposed changes helps frame the discussion. - -Based on the above characterization, "high-priority" changes (i.e. fixing -technical inaccuracies, broken links, etc.) can be proposed via pull requests -directly as they are straightforward to review. Other changes should be raised -as issues first so that the discussion can happen before you make major -modifications, which in principle saves you from wasting your time on -undesired changes. - -If you see a good tutorial, how-to or explanation that is not included in the -official documentation, you can suggest it to be added by `opening an issue on -GitHub <https://github.com/numpy/numpy/issues>`__. Similarly, opening issues to -suggest a tutorial, how-to or explanation that you can't find anywhere is a -great way to help the documentation team direct efforts towards what users are -looking for. `See this issue <https://github.com/numpy/numpy/issues/15760>`__ -for an example of how to do this. - -Finally, if you detect a typo or an error in the documentation, or would like to -suggest a different approach, you can also open an issue or submit a pull -request with your suggestion. Keep in mind that changes fixing -grammatical/spelling errors are welcome but not necessarily the highest -priority. "Grammatical correctness" often gets confused with "style" which can -result in unfruitful discussions that don't necessarily improve anything. -Changes that modify wording or rearrange phrasing without changing the technical -content are discouraged. If you think that a different wording improves clarity, -you should open an issue as noted above, but again, changes along these lines -very often tend to be highly subjective and not necessarily do much to improve -the quality of the documentation. - -**Final tips** - -- Don't worry if English is not your first language. Do your best - we'll revise - your content and make sure we fix any issues with the code or text. -- If you are unsure whether your tutorial is useful to the community, consider - submitting an issue on GitHub suggesting it, or asking on the mailing - list or Stack Overflow. -- If you are unfamiliar with git/GitHub or the process of submitting a pull - request (PR), check our :ref:`Contributor Guide <devindex>`. - -**Other interesting material** - -- `writethedocs.org <https://www.writethedocs.org/>`__ has a lot of interesting - resources for technical writing. -- Google offers two free `Technical Writing Courses - <https://developers.google.com/tech-writing>`__ -- `Software Carpentry <https://carpentries.github.io/curriculum-development/>`__ has a lot of - nice recommendations for creating educational material. +<http://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html>`__ + + +************************************************************ +Contributing indirectly +************************************************************ + +If you run across outside material that would be a useful addition to the +NumPy docs, let us know by `opening an issue <https://github.com/numpy/numpy/issues>`__. + +You don't have to contribute here to contribute to NumPy. You've contributed +if you write a tutorial on your blog, create a YouTube video, or answer questions +on Stack Overflow and other sites. + + +************************************************************ +Documentation reading +************************************************************ + +- The leading organization of technical writers, + `Write the Docs <https://www.writethedocs.org/>`__, + holds conferences, hosts learning resources, and runs a Slack channel. + +- "Every engineer is also a writer," says Google's + `collection of technical writing resources <https://developers.google.com/tech-writing>`__, + which includes free online courses for developers in planning and writing + documents. + +- `Software Carpentry's <https://software-carpentry.org/lessons>`__ mission is + teaching software to researchers. In addition to hosting the curriculum, the + website explains how to present ideas effectively. diff --git a/doc/source/f2py/python-usage.rst b/doc/source/f2py/python-usage.rst index a7f2b3d86..65c0cec64 100644 --- a/doc/source/f2py/python-usage.rst +++ b/doc/source/f2py/python-usage.rst @@ -71,7 +71,7 @@ Exceptions are NumPy arrays that must have type code ``'c'`` or A string can have arbitrary length when using it as a string argument to F2PY generated wrapper function. If the length is greater than -expected, the string is truncated. If the length is smaller that +expected, the string is truncated. If the length is smaller than expected, additional memory is allocated and filled with ``\0``. Because Python strings are immutable, an ``intent(inout)`` argument diff --git a/doc/source/reference/arrays.dtypes.rst b/doc/source/reference/arrays.dtypes.rst index f8d40b13c..8afbaeacc 100644 --- a/doc/source/reference/arrays.dtypes.rst +++ b/doc/source/reference/arrays.dtypes.rst @@ -206,7 +206,7 @@ Built-in Python types .. note:: All other types map to ``object_`` for convenience. Code should expect - that such types may map to a specific (new) dtype in future the future. + that such types may map to a specific (new) dtype in the future. Types with ``.dtype`` diff --git a/doc/source/reference/arrays.nditer.rst b/doc/source/reference/arrays.nditer.rst index 2db12a408..72a04f73e 100644 --- a/doc/source/reference/arrays.nditer.rst +++ b/doc/source/reference/arrays.nditer.rst @@ -10,6 +10,14 @@ Iterating Over Arrays ********************* +.. note:: + + Arrays support the iterator protocol and can be iterated over like Python + lists. See the :ref:`quickstart.indexing-slicing-and-iterating` section in + the Quickstart guide for basic usage and examples. The remainder of + this document presents the :class:`nditer` object and covers more + advanced usage. + The iterator object :class:`nditer`, introduced in NumPy 1.6, provides many flexible ways to visit all the elements of one or more arrays in a systematic fashion. This page introduces some basic ways to use the diff --git a/doc/source/reference/distutils.rst b/doc/source/reference/distutils.rst index 6057c7ed0..f201ba668 100644 --- a/doc/source/reference/distutils.rst +++ b/doc/source/reference/distutils.rst @@ -39,6 +39,7 @@ Modules in :mod:`numpy.distutils` :toctree: generated/ ccompiler + ccompiler_opt cpuinfo.cpu core.Extension exec_command diff --git a/doc/source/reference/figures/opt-infra.odg b/doc/source/reference/figures/opt-infra.odg Binary files differnew file mode 100644 index 000000000..a7b36f407 --- /dev/null +++ b/doc/source/reference/figures/opt-infra.odg diff --git a/doc/source/reference/figures/opt-infra.png b/doc/source/reference/figures/opt-infra.png Binary files differnew file mode 100644 index 000000000..e0b6f2316 --- /dev/null +++ b/doc/source/reference/figures/opt-infra.png diff --git a/doc/source/reference/index.rst b/doc/source/reference/index.rst index 2e1dcafa2..6eb74cd77 100644 --- a/doc/source/reference/index.rst +++ b/doc/source/reference/index.rst @@ -22,11 +22,13 @@ For learning how to use NumPy, see the :ref:`complete documentation <numpy_docs_ constants ufuncs routines + typing global_state distutils distutils_guide c-api/index internals + simd/simd-optimizations swig diff --git a/doc/source/reference/random/c-api.rst b/doc/source/reference/random/c-api.rst index 0d60f4d9e..63b0fdc2b 100644 --- a/doc/source/reference/random/c-api.rst +++ b/doc/source/reference/random/c-api.rst @@ -1,20 +1,14 @@ -Cython API for random ---------------------- +C API for random +---------------- .. currentmodule:: numpy.random -Typed versions of many of the `Generator` and `BitGenerator` methods as well as -the classes themselves can be accessed directly from Cython via - -.. code-block:: cython - - cimport numpy.random - -C API for random ----------------- +Access to various distributions below is available via Cython or C-wrapper +libraries like CFFI. All the functions accept a :c:type:`bitgen_t` as their +first argument. To access these from Cython or C, you must link with the +``npyrandom`` library which is part of the NumPy distribution, located in +``numpy/random/lib``. -Access to various distributions is available via Cython or C-wrapper libraries -like CFFI. All the functions accept a :c:type:`bitgen_t` as their first argument. .. c:type:: bitgen_t diff --git a/doc/source/reference/random/index.rst b/doc/source/reference/random/index.rst index d559f2327..13ce7c40c 100644 --- a/doc/source/reference/random/index.rst +++ b/doc/source/reference/random/index.rst @@ -23,7 +23,9 @@ number of different BitGenerators. It exposes many different probability distributions. See `NEP 19 <https://www.numpy.org/neps/ nep-0019-rng-policy.html>`_ for context on the updated random Numpy number routines. The legacy `RandomState` random number routines are still -available, but limited to a single BitGenerator. +available, but limited to a single BitGenerator. See :ref:`new-or-different` +for a complete list of improvements and differences from the legacy +``Randomstate``. For convenience and backward compatibility, a single `RandomState` instance's methods are imported into the numpy.random namespace, see @@ -41,13 +43,13 @@ properties than the legacy `MT19937` used in `RandomState`. .. code-block:: python - # Do this + # Do this (new version) from numpy.random import default_rng rng = default_rng() vals = rng.standard_normal(10) more_vals = rng.standard_normal(10) - # instead of this + # instead of this (legacy version) from numpy import random vals = random.standard_normal(10) more_vals = random.standard_normal(10) @@ -73,7 +75,7 @@ cleanup means that legacy and compatibility methods have been removed from ``seed`` removed Use `SeedSequence.spawn` =================== ============== ============ -See :ref:`new-or-different` for more information +See :ref:`new-or-different` for more information. Something like the following code can be used to support both ``RandomState`` and ``Generator``, with the understanding that the interfaces are slightly @@ -97,6 +99,30 @@ is wrapped with a `Generator`. from numpy.random import Generator, PCG64 rg = Generator(PCG64(12345)) rg.standard_normal() + +Here we use `default_rng` to create an instance of `Generator` to generate a +random float: + +>>> import numpy as np +>>> rng = np.random.default_rng(12345) +>>> print(rng) +Generator(PCG64) +>>> rfloat = rng.random() +>>> rfloat +0.22733602246716966 +>>> type(rfloat) +<class 'float'> + +Here we use `default_rng` to create an instance of `Generator` to generate 3 +random integers between 0 (inclusive) and 10 (exclusive): + +>>> import numpy as np +>>> rng = np.random.default_rng(12345) +>>> rints = rng.integers(low=0, high=10, size=3) +>>> rints +array([6, 2, 7]) +>>> type(rints[0]) +<class 'numpy.int64'> Introduction ------------ @@ -113,25 +139,36 @@ bit generator-provided stream and transforms them into more useful distributions, e.g., simulated normal random values. This structure allows alternative bit generators to be used with little code duplication. -The `Generator` is the user-facing object that is nearly identical to -`RandomState`. The canonical method to initialize a generator passes a -`PCG64` bit generator as the sole argument. +The `Generator` is the user-facing object that is nearly identical to the +legacy `RandomState`. It accepts a bit generator instance as an argument. +The default is currently `PCG64` but this may change in future versions. +As a convenience NumPy provides the `default_rng` function to hide these +details: + +>>> from numpy.random import default_rng +>>> rg = default_rng(12345) +>>> print(rg) +Generator(PCG64) +>>> print(rg.random()) +0.22733602246716966 + +One can also instantiate `Generator` directly with a `BitGenerator` instance. -.. code-block:: python +To use the default `PCG64` bit generator, one can instantiate it directly and +pass it to `Generator`: - from numpy.random import default_rng - rg = default_rng(12345) - rg.random() +>>> from numpy.random import Generator, PCG64 +>>> rg = Generator(PCG64(12345)) +>>> print(rg) +Generator(PCG64) -One can also instantiate `Generator` directly with a `BitGenerator` instance. -To use the older `MT19937` algorithm, one can instantiate it directly -and pass it to `Generator`. +Similarly to use the older `MT19937` bit generator (not recommended), one can +instantiate it directly and pass it to `Generator`: -.. code-block:: python - - from numpy.random import Generator, MT19937 - rg = Generator(MT19937(12345)) - rg.random() +>>> from numpy.random import Generator, MT19937 +>>> rg = Generator(MT19937(12345)) +>>> print(rg) +Generator(MT19937) What's New or Different ~~~~~~~~~~~~~~~~~~~~~~~ @@ -211,4 +248,3 @@ Original Source of the Generator and BitGenerators This package was developed independently of NumPy and was integrated in version 1.17.0. The original repo is at https://github.com/bashtage/randomgen. - diff --git a/doc/source/reference/routines.io.rst b/doc/source/reference/routines.io.rst index cf66eab49..2e119af9a 100644 --- a/doc/source/reference/routines.io.rst +++ b/doc/source/reference/routines.io.rst @@ -56,6 +56,7 @@ Memory mapping files :toctree: generated/ memmap + lib.format.open_memmap Text formatting options ----------------------- @@ -85,7 +86,6 @@ Data sources Binary Format Description ------------------------- .. autosummary:: - :template: autosummary/minimal_module.rst :toctree: generated/ lib.format diff --git a/doc/source/reference/simd/simd-optimizations-tables-diff.inc b/doc/source/reference/simd/simd-optimizations-tables-diff.inc new file mode 100644 index 000000000..41fa96703 --- /dev/null +++ b/doc/source/reference/simd/simd-optimizations-tables-diff.inc @@ -0,0 +1,37 @@ +.. generated via source/reference/simd/simd-optimizations.py + +x86::Intel Compiler - CPU feature names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. table:: + :align: left + + =========== ================================================================================================================== + Name Implies + =========== ================================================================================================================== + ``FMA3`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` **AVX2** + ``AVX2`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` **FMA3** + ``AVX512F`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` **AVX512CD** + =========== ================================================================================================================== + +.. note:: + The following features aren't supported by x86::Intel Compiler: + **XOP FMA4** + +x86::Microsoft Visual C/C++ - CPU feature names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. table:: + :align: left + + ============ ================================================================================================================================= + Name Implies + ============ ================================================================================================================================= + ``FMA3`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` **AVX2** + ``AVX2`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` **FMA3** + ``AVX512F`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` **AVX512CD** **AVX512_SKX** + ``AVX512CD`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` ``AVX512F`` **AVX512_SKX** + ============ ================================================================================================================================= + +.. note:: + The following features aren't supported by x86::Microsoft Visual C/C++: + **AVX512_KNL AVX512_KNM** + diff --git a/doc/source/reference/simd/simd-optimizations-tables.inc b/doc/source/reference/simd/simd-optimizations-tables.inc new file mode 100644 index 000000000..f038a91e1 --- /dev/null +++ b/doc/source/reference/simd/simd-optimizations-tables.inc @@ -0,0 +1,103 @@ +.. generated via source/reference/simd/simd-optimizations.py + +x86 - CPU feature names +~~~~~~~~~~~~~~~~~~~~~~~ +.. table:: + :align: left + + ============ ================================================================================================================= + Name Implies + ============ ================================================================================================================= + ``SSE`` ``SSE2`` + ``SSE2`` ``SSE`` + ``SSE3`` ``SSE`` ``SSE2`` + ``SSSE3`` ``SSE`` ``SSE2`` ``SSE3`` + ``SSE41`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` + ``POPCNT`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` + ``SSE42`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` + ``AVX`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` + ``XOP`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` + ``FMA4`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` + ``F16C`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` + ``FMA3`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` + ``AVX2`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` + ``AVX512F`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` + ``AVX512CD`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` ``AVX512F`` + ============ ================================================================================================================= + +x86 - Group names +~~~~~~~~~~~~~~~~~ +.. table:: + :align: left + + ============== ===================================================== =========================================================================================================================================================================== + Name Gather Implies + ============== ===================================================== =========================================================================================================================================================================== + ``AVX512_KNL`` ``AVX512ER`` ``AVX512PF`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` ``AVX512F`` ``AVX512CD`` + ``AVX512_KNM`` ``AVX5124FMAPS`` ``AVX5124VNNIW`` ``AVX512VPOPCNTDQ`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` ``AVX512F`` ``AVX512CD`` ``AVX512_KNL`` + ``AVX512_SKX`` ``AVX512VL`` ``AVX512BW`` ``AVX512DQ`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` ``AVX512F`` ``AVX512CD`` + ``AVX512_CLX`` ``AVX512VNNI`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` ``AVX512F`` ``AVX512CD`` ``AVX512_SKX`` + ``AVX512_CNL`` ``AVX512IFMA`` ``AVX512VBMI`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` ``AVX512F`` ``AVX512CD`` ``AVX512_SKX`` + ``AVX512_ICL`` ``AVX512VBMI2`` ``AVX512BITALG`` ``AVX512VPOPCNTDQ`` ``SSE`` ``SSE2`` ``SSE3`` ``SSSE3`` ``SSE41`` ``POPCNT`` ``SSE42`` ``AVX`` ``F16C`` ``FMA3`` ``AVX2`` ``AVX512F`` ``AVX512CD`` ``AVX512_SKX`` ``AVX512_CLX`` ``AVX512_CNL`` + ============== ===================================================== =========================================================================================================================================================================== + +IBM/POWER big-endian - CPU feature names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. table:: + :align: left + + ======== ================ + Name Implies + ======== ================ + ``VSX`` + ``VSX2`` ``VSX`` + ``VSX3`` ``VSX`` ``VSX2`` + ======== ================ + +IBM/POWER little-endian - CPU feature names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. table:: + :align: left + + ======== ================ + Name Implies + ======== ================ + ``VSX`` ``VSX2`` + ``VSX2`` ``VSX`` + ``VSX3`` ``VSX`` ``VSX2`` + ======== ================ + +ARMv7/A32 - CPU feature names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. table:: + :align: left + + ============== =========================================================== + Name Implies + ============== =========================================================== + ``NEON`` + ``NEON_FP16`` ``NEON`` + ``NEON_VFPV4`` ``NEON`` ``NEON_FP16`` + ``ASIMD`` ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` + ``ASIMDHP`` ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` ``ASIMD`` + ``ASIMDDP`` ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` ``ASIMD`` + ``ASIMDFHM`` ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` ``ASIMD`` ``ASIMDHP`` + ============== =========================================================== + +ARMv8/A64 - CPU feature names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. table:: + :align: left + + ============== =========================================================== + Name Implies + ============== =========================================================== + ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` ``ASIMD`` + ``NEON_FP16`` ``NEON`` ``NEON_VFPV4`` ``ASIMD`` + ``NEON_VFPV4`` ``NEON`` ``NEON_FP16`` ``ASIMD`` + ``ASIMD`` ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` + ``ASIMDHP`` ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` ``ASIMD`` + ``ASIMDDP`` ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` ``ASIMD`` + ``ASIMDFHM`` ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` ``ASIMD`` ``ASIMDHP`` + ============== =========================================================== + diff --git a/doc/source/reference/simd/simd-optimizations.py b/doc/source/reference/simd/simd-optimizations.py new file mode 100644 index 000000000..5d6da50e3 --- /dev/null +++ b/doc/source/reference/simd/simd-optimizations.py @@ -0,0 +1,190 @@ +""" +Generate CPU features tables from CCompilerOpt +""" +from os import sys, path +gen_path = path.dirname(path.realpath(__file__)) +#sys.path.append(path.abspath(path.join(gen_path, *([".."]*4), "numpy", "distutils"))) +#from ccompiler_opt import CCompilerOpt +from numpy.distutils.ccompiler_opt import CCompilerOpt + +class FakeCCompilerOpt(CCompilerOpt): + fake_info = "" + # disable caching no need for it + conf_nocache = True + def __init__(self, *args, **kwargs): + no_cc = None + CCompilerOpt.__init__(self, no_cc, **kwargs) + def dist_compile(self, sources, flags, **kwargs): + return sources + def dist_info(self): + return FakeCCompilerOpt.fake_info + @staticmethod + def dist_log(*args, stderr=False): + # avoid printing + pass + def feature_test(self, name, force_flags=None): + # To speed up + return True + + def gen_features_table(self, features, ignore_groups=True, + field_names=["Name", "Implies"], + fstyle=None, fstyle_implies=None, **kwargs): + rows = [] + if fstyle is None: + fstyle = lambda ft: f'``{ft}``' + if fstyle_implies is None: + fstyle_implies = lambda origin, ft: fstyle(ft) + for f in self.feature_sorted(features): + is_group = "group" in self.feature_supported.get(f, {}) + if ignore_groups and is_group: + continue + implies = self.feature_sorted(self.feature_implies(f)) + implies = ' '.join([fstyle_implies(f, i) for i in implies]) + rows.append([fstyle(f), implies]) + if rows: + return self.gen_rst_table(field_names, rows, **kwargs) + + def gen_gfeatures_table(self, features, + field_names=["Name", "Gather", "Implies"], + fstyle=None, fstyle_implies=None, **kwargs): + rows = [] + if fstyle is None: + fstyle = lambda ft: f'``{ft}``' + if fstyle_implies is None: + fstyle_implies = lambda origin, ft: fstyle(ft) + for f in self.feature_sorted(features): + gather = self.feature_supported.get(f, {}).get("group", None) + if not gather: + continue + implies = self.feature_sorted(self.feature_implies(f)) + implies = ' '.join([fstyle_implies(f, i) for i in implies]) + gather = ' '.join([fstyle_implies(f, i) for i in gather]) + rows.append([fstyle(f), gather, implies]) + if rows: + return self.gen_rst_table(field_names, rows, **kwargs) + + def gen_rst_table(self, field_names, rows, tab_size=4): + assert(not rows or len(field_names) == len(rows[0])) + rows.append(field_names) + fld_len = len(field_names) + cls_len = [max(len(c[i]) for c in rows) for i in range(fld_len)] + del rows[-1] + cformat = ' '.join('{:<%d}' % i for i in cls_len) + border = cformat.format(*['='*i for i in cls_len]) + + rows = [cformat.format(*row) for row in rows] + # header + rows = [border, cformat.format(*field_names), border] + rows + # footer + rows += [border] + # add left margin + rows = [(' ' * tab_size) + r for r in rows] + return '\n'.join(rows) + +def features_table_sections(name, ftable=None, gtable=None, tab_size=4): + tab = ' '*tab_size + content = '' + if ftable: + title = f"{name} - CPU feature names" + content = ( + f"{title}\n{'~'*len(title)}" + f"\n.. table::\n{tab}:align: left\n\n" + f"{ftable}\n\n" + ) + if gtable: + title = f"{name} - Group names" + content += ( + f"{title}\n{'~'*len(title)}" + f"\n.. table::\n{tab}:align: left\n\n" + f"{gtable}\n\n" + ) + return content + +def features_table(arch, cc="gcc", pretty_name=None, **kwargs): + FakeCCompilerOpt.fake_info = arch + cc + ccopt = FakeCCompilerOpt(cpu_baseline="max") + features = ccopt.cpu_baseline_names() + ftable = ccopt.gen_features_table(features, **kwargs) + gtable = ccopt.gen_gfeatures_table(features, **kwargs) + + if not pretty_name: + pretty_name = arch + '/' + cc + return features_table_sections(pretty_name, ftable, gtable, **kwargs) + +def features_table_diff(arch, cc, cc_vs="gcc", pretty_name=None, **kwargs): + FakeCCompilerOpt.fake_info = arch + cc + ccopt = FakeCCompilerOpt(cpu_baseline="max") + fnames = ccopt.cpu_baseline_names() + features = {f:ccopt.feature_implies(f) for f in fnames} + + FakeCCompilerOpt.fake_info = arch + cc_vs + ccopt_vs = FakeCCompilerOpt(cpu_baseline="max") + fnames_vs = ccopt_vs.cpu_baseline_names() + features_vs = {f:ccopt_vs.feature_implies(f) for f in fnames_vs} + + common = set(fnames).intersection(fnames_vs) + extra_avl = set(fnames).difference(fnames_vs) + not_avl = set(fnames_vs).difference(fnames) + diff_impl_f = {f:features[f].difference(features_vs[f]) for f in common} + diff_impl = {k for k, v in diff_impl_f.items() if v} + + fbold = lambda ft: f'**{ft}**' if ft in extra_avl else f'``{ft}``' + fbold_implies = lambda origin, ft: ( + f'**{ft}**' if ft in diff_impl_f.get(origin, {}) else f'``{ft}``' + ) + diff_all = diff_impl.union(extra_avl) + ftable = ccopt.gen_features_table( + diff_all, fstyle=fbold, fstyle_implies=fbold_implies, **kwargs + ) + gtable = ccopt.gen_gfeatures_table( + diff_all, fstyle=fbold, fstyle_implies=fbold_implies, **kwargs + ) + if not pretty_name: + pretty_name = arch + '/' + cc + content = features_table_sections(pretty_name, ftable, gtable, **kwargs) + + if not_avl: + not_avl = ccopt_vs.feature_sorted(not_avl) + not_avl = ' '.join(not_avl) + content += ( + ".. note::\n" + f" The following features aren't supported by {pretty_name}:\n" + f" **{not_avl}**\n\n" + ) + return content + +if __name__ == '__main__': + pretty_names = { + "PPC64": "IBM/POWER big-endian", + "PPC64LE": "IBM/POWER little-endian", + "ARMHF": "ARMv7/A32", + "AARCH64": "ARMv8/A64", + "ICC": "Intel Compiler", + # "ICCW": "Intel Compiler msvc-like", + "MSVC": "Microsoft Visual C/C++" + } + with open(path.join(gen_path, 'simd-optimizations-tables.inc'), 'wt') as fd: + fd.write(f'.. generated via {__file__}\n\n') + for arch in ( + ("x86", "PPC64", "PPC64LE", "ARMHF", "AARCH64") + ): + pretty_name = pretty_names.get(arch, arch) + table = features_table(arch=arch, pretty_name=pretty_name) + assert(table) + fd.write(table) + + with open(path.join(gen_path, 'simd-optimizations-tables-diff.inc'), 'wt') as fd: + fd.write(f'.. generated via {__file__}\n\n') + for arch, cc_names in ( + ("x86", ("clang", "ICC", "MSVC")), + ("PPC64", ("clang",)), + ("PPC64LE", ("clang",)), + ("ARMHF", ("clang",)), + ("AARCH64", ("clang",)) + ): + arch_pname = pretty_names.get(arch, arch) + for cc in cc_names: + pretty_name = f"{arch_pname}::{pretty_names.get(cc, cc)}" + table = features_table_diff(arch=arch, cc=cc, pretty_name=pretty_name) + if table: + fd.write(table) diff --git a/doc/source/reference/simd/simd-optimizations.rst b/doc/source/reference/simd/simd-optimizations.rst new file mode 100644 index 000000000..59a4892b2 --- /dev/null +++ b/doc/source/reference/simd/simd-optimizations.rst @@ -0,0 +1,527 @@ +****************** +SIMD Optimizations +****************** + +NumPy provides a set of macros that define `Universal Intrinsics`_ to +abstract out typical platform-specific intrinsics so SIMD code needs to be +written only once. There are three layers: + +- Code is *written* using the universal intrinsic macros, with guards that + will enable use of the macros only when the compiler recognizes them. + In NumPy, these are used to construct multiple ufunc loops. Current policy is + to create three loops: One loop is the default and uses no intrinsics. One + uses the minimum intrinsics required on the architecture. And the third is + written using the maximum set of intrinsics possible. +- At *compile* time, a distutils command is used to define the minimum and + maximum features to support, based on user choice and compiler support. The + appropriate macros are overlayed with the platform / architecture intrinsics, + and the three loops are compiled. +- At *runtime import*, the CPU is probed for the set of supported intrinsic + features. A mechanism is used to grab the pointer to the most appropriate + function, and this will be the one called for the function. + + +Build options for compilation +============================= + +- ``--cpu-baseline``: minimal set of required optimizations. Default + value is ``min`` which provides the minimum CPU features that can + safely run on a wide range of platforms within the processor family. + +- ``--cpu-dispatch``: dispatched set of additional optimizations. + The default value is ``max -xop -fma4`` which enables all CPU + features, except for AMD legacy features(in case of X86). + +The command arguments are available in ``build``, ``build_clib``, and +``build_ext``. +if ``build_clib`` or ``build_ext`` are not specified by the user, the arguments of +``build`` will be used instead, which also holds the default values. + +Optimization names can be CPU features or groups of features that gather +several features or :ref:`special options <special-options>` to perform a series of procedures. + + +The following tables show the current supported optimizations sorted from the lowest to the highest interest. + +.. include:: simd-optimizations-tables.inc + +---- + +.. _tables-diff: + +While the above tables are based on the GCC Compiler, the following tables showing the differences in the +other compilers: + +.. include:: simd-optimizations-tables-diff.inc + +.. _special-options: + +Special options +~~~~~~~~~~~~~~~ + +- ``NONE``: enable no features + +- ``NATIVE``: Enables all CPU features that supported by the current + machine, this operation is based on the compiler flags (``-march=native, -xHost, /QxHost``) + +- ``MIN``: Enables the minimum CPU features that can safely run on a wide range of platforms: + + .. table:: + :align: left + + ====================================== ======================================= + For Arch Returns + ====================================== ======================================= + ``x86`` ``SSE`` ``SSE2`` + ``x86`` ``64-bit mode`` ``SSE`` ``SSE2`` ``SSE3`` + ``IBM/POWER`` ``big-endian mode`` ``NONE`` + ``IBM/POWER`` ``little-endian mode`` ``VSX`` ``VSX2`` + ``ARMHF`` ``NONE`` + ``ARM64`` ``AARCH64`` ``NEON`` ``NEON_FP16`` ``NEON_VFPV4`` + ``ASIMD`` + ====================================== ======================================= + +- ``MAX``: Enables all supported CPU features by the Compiler and platform. + +- ``Operators-/+``: remove or add features, useful with options ``MAX``, ``MIN`` and ``NATIVE``. + +NOTES +~~~~~~~~~~~~~ +- CPU features and other options are case-insensitive. + +- The order of the requsted optimizations doesn't matter. + +- Either commas or spaces can be used as a separator, e.g. ``--cpu-dispatch``\ = + "avx2 avx512f" or ``--cpu-dispatch``\ = "avx2, avx512f" both work, but the + arguments must be enclosed in quotes. + +- The operand ``+`` is only added for nominal reasons, For example: + ``--cpu-basline= "min avx2"`` is equivalent to ``--cpu-basline="min + avx2"``. + ``--cpu-basline="min,avx2"`` is equivalent to ``--cpu-basline`="min,+avx2"`` + +- If the CPU feature is not supported by the user platform or + compiler, it will be skipped rather than raising a fatal error. + +- Any specified CPU feature to ``--cpu-dispatch`` will be skipped if + it's part of CPU baseline features + +- The ``--cpu-baseline`` argument force-enables implied features, + e.g. ``--cpu-baseline``\ ="sse42" is equivalent to + ``--cpu-baseline``\ ="sse sse2 sse3 ssse3 sse41 popcnt sse42" + +- The value of ``--cpu-baseline`` will be treated as "native" if + compiler native flag ``-march=native`` or ``-xHost`` or ``QxHost`` is + enabled through environment variable ``CFLAGS`` + +- The validation process for the requsted optimizations when it comes to + ``--cpu-baseline`` isn't strict. For example, if the user requested + ``AVX2`` but the compiler doesn't support it then we just skip it and return + the maximum optimization that the compiler can handle depending on the + implied features of ``AVX2``, let us assume ``AVX``. + +- The user should always check the final report through the build log + to verify the enabled features. + +Special cases +~~~~~~~~~~~~~ + +**Interrelated CPU features**: Some exceptional conditions force us to link some features together when it come to certain compilers or architectures, resulting in the impossibility of building them separately. +These conditions can be divided into two parts, as follows: + +- **Architectural compatibility**: The need to align certain CPU features that are assured + to be supported by successive generations of the same architecture, for example: + + - On ppc64le `VSX(ISA 2.06)` and `VSX2(ISA 2.07)` both imply one another since the + first generation that supports little-endian mode is Power-8`(ISA 2.07)` + - On AArch64 `NEON` `FP16` `VFPV4` `ASIMD` implies each other since they are part of the + hardware baseline. + +- **Compilation compatibility**: Not all **C/C++** compilers provide independent support for all CPU + features. For example, **Intel**'s compiler doesn't provide separated flags for `AVX2` and `FMA3`, + it makes sense since all Intel CPUs that comes with `AVX2` also support `FMA3` and vice versa, + but this approach is incompatible with other **x86** CPUs from **AMD** or **VIA**. + Therefore, there are differences in the depiction of CPU features between the C/C++ compilers, + as shown in the :ref:`tables above <tables-diff>`. + + +Behaviors and Errors +~~~~~~~~~~~~~~~~~~~~ + + + +Usage and Examples +~~~~~~~~~~~~~~~~~~ + +Report and Trace +~~~~~~~~~~~~~~~~ + +Understanding CPU Dispatching, How the NumPy dispatcher works? +============================================================== + +NumPy dispatcher is based on multi-source compiling, which means taking +a certain source and compiling it multiple times with different compiler +flags and also with different **C** definitions that affect the code +paths to enable certain instruction-sets for each compiled object +depending on the required optimizations, then combining the returned +objects together. + +.. figure:: ../figures/opt-infra.png + +This mechanism should support all compilers and it doesn't require any +compiler-specific extension, but at the same time it is adds a few steps to +normal compilation that are explained as follows: + +1- Configuration +~~~~~~~~~~~~~~~~ + +Configuring the required optimization by the user before starting to build the +source files via the two command arguments as explained above: + +- ``--cpu-baseline``: minimal set of required optimizations. + +- ``--cpu-dispatch``: dispatched set of additional optimizations. + + +2- Discovering the environment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this part, we check the compiler and platform architecture +and cache some of the intermediary results to speed up rebuilding. + +3- Validating the requested optimizations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By testing them against the compiler, and seeing what the compiler can +support according to the requested optimizations. + +4- Generating the main configuration header +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The generated header ``_cpu_dispatch.h`` contains all the definitions and +headers of instruction-sets for the required optimizations that have been +validated during the previous step. + +It also contains extra C definitions that are used for defining NumPy's +Python-level module attributes ``__cpu_baseline__`` and ``__cpu_dispaٍtch__``. + +**What is in this header?** + +The example header was dynamically generated by gcc on an X86 machine. +The compiler supports ``--cpu-baseline="sse sse2 sse3"`` and +``--cpu-dispatch="ssse3 sse41"``, and the result is below. + +.. code:: c + + // The header should be located at numpy/numpy/core/src/common/_cpu_dispatch.h + /**NOTE + ** C definitions prefixed with "NPY_HAVE_" represent + ** the required optimzations. + ** + ** C definitions prefixed with 'NPY__CPU_TARGET_' are protected and + ** shouldn't be used by any NumPy C sources. + */ + /******* baseline features *******/ + /** SSE **/ + #define NPY_HAVE_SSE 1 + #include <xmmintrin.h> + /** SSE2 **/ + #define NPY_HAVE_SSE2 1 + #include <emmintrin.h> + /** SSE3 **/ + #define NPY_HAVE_SSE3 1 + #include <pmmintrin.h> + + /******* dispatch-able features *******/ + #ifdef NPY__CPU_TARGET_SSSE3 + /** SSSE3 **/ + #define NPY_HAVE_SSSE3 1 + #include <tmmintrin.h> + #endif + #ifdef NPY__CPU_TARGET_SSE41 + /** SSE41 **/ + #define NPY_HAVE_SSE41 1 + #include <smmintrin.h> + #endif + +**Baseline features** are the minimal set of required optimizations configured +via ``--cpu-baseline``. They have no preprocessor guards and they're +always on, which means they can be used in any source. + +Does this mean NumPy's infrastructure passes the compiler's flags of +baseline features to all sources? + +Definitely, yes. But the :ref:`dispatch-able sources <dispatchable-sources>` are +treated differently. + +What if the user specifies certain **baseline features** during the +build but at runtime the machine doesn't support even these +features? Will the compiled code be called via one of these definitions, or +maybe the compiler itself auto-generated/vectorized certain piece of code +based on the provided command line compiler flags? + +During the loading of the NumPy module, there's a validation step +which detects this behavior. It will raise a Python runtime error to inform the +user. This is to prevent the CPU reaching an illegal instruction error causing +a segfault. + +**Dispatch-able features** are our dispatched set of additional optimizations +that were configured via ``--cpu-dispatch``. They are not activated by +default and are always guarded by other C definitions prefixed with +``NPY__CPU_TARGET_``. C definitions ``NPY__CPU_TARGET_`` are only +enabled within **dispatch-able sources**. + +.. _dispatchable-sources: + +5- Dispatch-able sources and configuration statements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Dispatch-able sources are special **C** files that can be compiled multiple +times with different compiler flags and also with different **C** +definitions. These affect code paths to enable certain +instruction-sets for each compiled object according to "**the +configuration statements**" that must be declared between a **C** +comment\ ``(/**/)`` and start with a special mark **@targets** at the +top of each dispatch-able source. At the same time, dispatch-able +sources will be treated as normal **C** sources if the optimization was +disabled by the command argument ``--disable-optimization`` . + +**What are configuration statements?** + +Configuration statements are sort of keywords combined together to +determine the required optimization for the dispatch-able source. + +Example: + +.. code:: c + + /*@targets avx2 avx512f vsx2 vsx3 asimd asimdhp */ + // C code + +The keywords mainly represent the additional optimizations configured +through ``--cpu-dispatch``, but it can also represent other options such as: + +- Target groups: pre-configured configuration statements used for + managing the required optimizations from outside the dispatch-able source. + +- Policies: collections of options used for changing the default + behaviors or forcing the compilers to perform certain things. + +- "baseline": a unique keyword represents the minimal optimizations + that configured through ``--cpu-baseline`` + +**Numpy's infrastructure handles dispatch-able sources in four steps**: + +- **(A) Recognition**: Just like source templates and F2PY, the + dispatch-able sources requires a special extension ``*.dispatch.c`` + to mark C dispatch-able source files, and for C++ + ``*.dispatch.cpp`` or ``*.dispatch.cxx`` + **NOTE**: C++ not supported yet. + +- **(B) Parsing and validating**: In this step, the + dispatch-able sources that had been filtered by the previous step + are parsed and validated by the configuration statements for each one + of them one by one in order to determine the required optimizations. + +- **(C) Wrapping**: This is the approach taken by NumPy's + infrastructure, which has proved to be sufficiently flexible in order + to compile a single source multiple times with different **C** + definitions and flags that affect the code paths. The process is + achieved by creating a temporary **C** source for each required + optimization that related to the additional optimization, which + contains the declarations of the **C** definitions and includes the + involved source via the **C** directive **#include**. For more + clarification take a look at the following code for AVX512F : + + .. code:: c + + /* + * this definition is used by NumPy utilities as suffixes for the + * exported symbols + */ + #define NPY__CPU_TARGET_CURRENT AVX512F + /* + * The following definitions enable + * definitions of the dispatch-able features that are defined within the main + * configuration header. These are definitions for the implied features. + */ + #define NPY__CPU_TARGET_SSE + #define NPY__CPU_TARGET_SSE2 + #define NPY__CPU_TARGET_SSE3 + #define NPY__CPU_TARGET_SSSE3 + #define NPY__CPU_TARGET_SSE41 + #define NPY__CPU_TARGET_POPCNT + #define NPY__CPU_TARGET_SSE42 + #define NPY__CPU_TARGET_AVX + #define NPY__CPU_TARGET_F16C + #define NPY__CPU_TARGET_FMA3 + #define NPY__CPU_TARGET_AVX2 + #define NPY__CPU_TARGET_AVX512F + // our dispatch-able source + #include "/the/absuolate/path/of/hello.dispatch.c" + +- **(D) Dispatch-able configuration header**: The infrastructure + generates a config header for each dispatch-able source, this header + mainly contains two abstract **C** macros used for identifying the + generated objects, so they can be used for runtime dispatching + certain symbols from the generated objects by any **C** source. It is + also used for forward declarations. + + The generated header takes the name of the dispatch-able source after + excluding the extension and replace it with '**.h**', for example + assume we have a dispatch-able source called **hello.dispatch.c** and + contains the following: + + .. code:: c + + // hello.dispatch.c + /*@targets baseline sse42 avx512f */ + #include <stdio.h> + #include "numpy/utils.h" // NPY_CAT, NPY_TOSTR + + #ifndef NPY__CPU_TARGET_CURRENT + // wrapping the dispatch-able source only happens to the addtional optimizations + // but if the keyword 'baseline' provided within the configuration statments, + // the infrastructure will add extra compiling for the dispatch-able source by + // passing it as-is to the compiler without any changes. + #define CURRENT_TARGET(X) X + #define NPY__CPU_TARGET_CURRENT baseline // for printing only + #else + // since we reach to this point, that's mean we're dealing with + // the addtional optimizations, so it could be SSE42 or AVX512F + #define CURRENT_TARGET(X) NPY_CAT(NPY_CAT(X, _), NPY__CPU_TARGET_CURRENT) + #endif + // Macro 'CURRENT_TARGET' adding the current target as suffux to the exported symbols, + // to avoid linking duplications, NumPy already has a macro called + // 'NPY_CPU_DISPATCH_CURFX' similar to it, located at + // numpy/numpy/core/src/common/npy_cpu_dispatch.h + // NOTE: we tend to not adding suffixes to the baseline exported symbols + void CURRENT_TARGET(simd_whoami)(const char *extra_info) + { + printf("I'm " NPY_TOSTR(NPY__CPU_TARGET_CURRENT) ", %s\n", extra_info); + } + + Now assume you attached **hello.dispatch.c** to the source tree, then + the infrastructure should generate a temporary config header called + **hello.dispatch.h** that can be reached by any source in the source + tree, and it should contain the following code : + + .. code:: c + + #ifndef NPY__CPU_DISPATCH_EXPAND_ + // To expand the macro calls in this header + #define NPY__CPU_DISPATCH_EXPAND_(X) X + #endif + // Undefining the following macros, due to the possibility of including config headers + // multiple times within the same source and since each config header represents + // different required optimizations according to the specified configuration + // statements in the dispatch-able source that derived from it. + #undef NPY__CPU_DISPATCH_BASELINE_CALL + #undef NPY__CPU_DISPATCH_CALL + // nothing strange here, just a normal preprocessor callback + // enabled only if 'baseline' spesfied withiin the configration statments + #define NPY__CPU_DISPATCH_BASELINE_CALL(CB, ...) \ + NPY__CPU_DISPATCH_EXPAND_(CB(__VA_ARGS__)) + // 'NPY__CPU_DISPATCH_CALL' is an abstract macro is used for dispatching + // the required optimizations that specified within the configuration statements. + // + // @param CHK, Expected a macro that can be used to detect CPU features + // in runtime, which takes a CPU feature name without string quotes and + // returns the testing result in a shape of boolean value. + // NumPy already has macro called "NPY_CPU_HAVE", which fit this requirment. + // + // @param CB, a callback macro that expected to be called multiple times depending + // on the required optimizations, the callback should receive the following arguments: + // 1- The pending calls of @param CHK filled up with the required CPU features, + // that need to be tested first in runtime before executing call belong to + // the compiled object. + // 2- The required optimization name, same as in 'NPY__CPU_TARGET_CURRENT' + // 3- Extra arguments in the macro itself + // + // By default the callback calls are sorted depending on the highest interest + // unless the policy "$keep_sort" was in place within the configuration statements + // see "Dive into the CPU dispatcher" for more clarification. + #define NPY__CPU_DISPATCH_CALL(CHK, CB, ...) \ + NPY__CPU_DISPATCH_EXPAND_(CB((CHK(AVX512F)), AVX512F, __VA_ARGS__)) \ + NPY__CPU_DISPATCH_EXPAND_(CB((CHK(SSE)&&CHK(SSE2)&&CHK(SSE3)&&CHK(SSSE3)&&CHK(SSE41)), SSE41, __VA_ARGS__)) + + An example of using the config header in light of the above: + + .. code:: c + + // NOTE: The following macros are only defined for demonstration purposes only. + // NumPy already has a collections of macros located at + // numpy/numpy/core/src/common/npy_cpu_dispatch.h, that covers all dispatching + // and declarations scenarios. + + #include "numpy/npy_cpu_features.h" // NPY_CPU_HAVE + #include "numpy/utils.h" // NPY_CAT, NPY_EXPAND + + // An example for setting a macro that calls all the exported symbols at once + // after checking if they're supported by the running machine. + #define DISPATCH_CALL_ALL(FN, ARGS) \ + NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, DISPATCH_CALL_ALL_CB, FN, ARGS) \ + NPY__CPU_DISPATCH_BASELINE_CALL(DISPATCH_CALL_BASELINE_ALL_CB, FN, ARGS) + // The preprocessor callbacks. + // The same suffixes as we define it in the dispatch-able source. + #define DISPATCH_CALL_ALL_CB(CHECK, TARGET_NAME, FN, ARGS) \ + if (CHECK) { NPY_CAT(NPY_CAT(FN, _), TARGET_NAME) ARGS; } + #define DISPATCH_CALL_BASELINE_ALL_CB(FN, ARGS) \ + FN NPY_EXPAND(ARGS); + + // An example for setting a macro that calls the exported symbols of highest + // interest optimization, after checking if they're supported by the running machine. + #define DISPATCH_CALL_HIGH(FN, ARGS) \ + if (0) {} \ + NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, DISPATCH_CALL_HIGH_CB, FN, ARGS) \ + NPY__CPU_DISPATCH_BASELINE_CALL(DISPATCH_CALL_BASELINE_HIGH_CB, FN, ARGS) + // The preprocessor callbacks + // The same suffixes as we define it in the dispatch-able source. + #define DISPATCH_CALL_HIGH_CB(CHECK, TARGET_NAME, FN, ARGS) \ + else if (CHECK) { NPY_CAT(NPY_CAT(FN, _), TARGET_NAME) ARGS; } + #define DISPATCH_CALL_BASELINE_HIGH_CB(FN, ARGS) \ + else { FN NPY_EXPAND(ARGS); } + + // NumPy has a macro called 'NPY_CPU_DISPATCH_DECLARE' can be used + // for forward declrations any kind of prototypes based on + // 'NPY__CPU_DISPATCH_CALL' and 'NPY__CPU_DISPATCH_BASELINE_CALL'. + // However in this example, we just handle it manually. + void simd_whoami(const char *extra_info); + void simd_whoami_AVX512F(const char *extra_info); + void simd_whoami_SSE41(const char *extra_info); + + void trigger_me(void) + { + // bring the auto-gernreated config header + // which contains config macros 'NPY__CPU_DISPATCH_CALL' and + // 'NPY__CPU_DISPATCH_BASELINE_CALL'. + // it highely recomaned to include the config header before exectuing + // the dispatching macros in case if there's another header in the scope. + #include "hello.dispatch.h" + DISPATCH_CALL_ALL(simd_whoami, ("all")) + DISPATCH_CALL_HIGH(simd_whoami, ("the highest interest")) + // An example of including multiple config headers in the same source + // #include "hello2.dispatch.h" + // DISPATCH_CALL_HIGH(another_function, ("the highest interest")) + } + + +Dive into the CPU dispatcher +============================ + +The baseline +~~~~~~~~~~~~ + +Dispatcher +~~~~~~~~~~ + +Groups and Policies +~~~~~~~~~~~~~~~~~~~ + +Examples +~~~~~~~~ + +Report and Trace +~~~~~~~~~~~~~~~~ + + +.. _`Universal Intrinsics`: https://numpy.org/neps/nep-0038-SIMD-optimizations.html diff --git a/doc/source/reference/typing.rst b/doc/source/reference/typing.rst new file mode 100644 index 000000000..c948bc4be --- /dev/null +++ b/doc/source/reference/typing.rst @@ -0,0 +1,2 @@ +.. _typing: +.. automodule:: numpy.typing diff --git a/doc/source/reference/ufuncs.rst b/doc/source/reference/ufuncs.rst index 8f506dd8b..a0a5a4a06 100644 --- a/doc/source/reference/ufuncs.rst +++ b/doc/source/reference/ufuncs.rst @@ -335,6 +335,19 @@ advanced usage and will not typically be used. Note that outputs not explicitly filled are left with their uninitialized values. + .. versionadded:: 1.13 + + Operations where ufunc input and output operands have memory overlap are + defined to be the same as for equivalent operations where there + is no memory overlap. Operations affected make temporary copies + as needed to eliminate data dependency. As detecting these cases + is computationally expensive, a heuristic is used, which may in rare + cases result in needless temporary copies. For operations where the + data dependency is simple enough for the heuristic to analyze, + temporary copies will not be made even if the arrays overlap, if it + can be deduced copies are not necessary. As an example, + ``np.add(a, b, out=a)`` will not involve copies. + *where* .. versionadded:: 1.7 diff --git a/doc/source/release/1.19.0-notes.rst b/doc/source/release/1.19.0-notes.rst index 35aaf8e4a..8f5c2c0ce 100644 --- a/doc/source/release/1.19.0-notes.rst +++ b/doc/source/release/1.19.0-notes.rst @@ -1,113 +1,47 @@ +.. currentmodule:: numpy + ========================== NumPy 1.19.0 Release Notes ========================== +This NumPy release is marked by the removal of much technical debt: support for +Python 2 has been removed, many deprecations have been expired, and +documentation has been improved. The polishing of the random module continues +apace with bug fixes and better usability from Cython. + +The Python versions supported for this release are 3.6-3.8. Downstream +developers should use Cython >= 0.29.16 for Python 3.8 support and +OpenBLAS >= 3.7 to avoid problems on the Skylake architecture. Highlights ========== -* Code compatibility with Python versions < 3.5 (including Python 2) was - dropped from both the python and C code. The shims in numpy.compat will +* Code compatibility with Python versions < 3.6 (including Python 2) was + dropped from both the python and C code. The shims in ``numpy.compat`` will remain to support third-party packages, but they may be deprecated in a - future release. + future release. Note that 1.19.x will *not* compile with earlier versions of + Python due to the use of f-strings. (`gh-15233 <https://github.com/numpy/numpy/pull/15233>`__) -Deprecations -============ - - -Deprecate automatic ``dtype=object`` for ragged input ------------------------------------------------------ -Calling ``np.array([[1, [1, 2, 3]])`` will issue a ``DeprecationWarning`` as -per `NEP 34`_. Users should explicitly use ``dtype=object`` to avoid the -warning. - -.. _`NEP 34`: https://numpy.org/neps/nep-0034-infer-dtype-is-object.html - -(`gh-15119 <https://github.com/numpy/numpy/pull/15119>`__) - -Passing ``shape=0`` to factory functions in ``numpy.rec`` is deprecated ------------------------------------------------------------------------ - -``0`` is treated as a special case and is aliased to ``None`` in the functions: - -* `numpy.core.records.fromarrays` -* `numpy.core.records.fromrecords` -* `numpy.core.records.fromstring` -* `numpy.core.records.fromfile` - -In future, ``0`` will not be special cased, and will be treated as an array -length like any other integer. - -(`gh-15217 <https://github.com/numpy/numpy/pull/15217>`__) - -Deprecation of probably unused C-API functions ----------------------------------------------- -The following C-API functions are probably unused and have been -deprecated: - -* ``PyArray_GetArrayParamsFromObject`` -* ``PyUFunc_GenericFunction`` -* ``PyUFunc_SetUsesArraysAsData`` - -In most cases ``PyArray_GetArrayParamsFromObject`` should be replaced -by converting to an array, while ``PyUFunc_GenericFunction`` can be -replaced with ``PyObject_Call`` (see documentation for details). - -(`gh-15427 <https://github.com/numpy/numpy/pull/15427>`__) - -Converting certain types to dtypes is Deprecated ------------------------------------------------- -The super classes of scalar types, such as ``np.integer``, ``np.generic``, -or ``np.inexact`` will now give a deprecation warning when converted -to a dtype (or used in a dtype keyword argument). -The reason for this is that `np.integer` is converted to ``np.int_``, -while it would be expected to represent *any* integer (e.g. also -``int8``, ``int16``, etc. -For example, ``dtype=np.floating`` is currently identical to -``dtype=np.float64``, even though also ``np.float32`` is a subclass of -``np.floating``. - -(`gh-15534 <https://github.com/numpy/numpy/pull/15534>`__) - -Deprecation of `round` for ``np.complexfloating`` scalars ------------------------------------------------------------ - -Output of the ``__round__`` dunder method and consequently the Python built-in -`round` has been deprecated on complex scalars. This does not affect -`np.round`. - -(`gh-15840 <https://github.com/numpy/numpy/pull/15840>`__) - -``numpy.ndarray.tostring()`` is deprecated in favor of ``tobytes()`` --------------------------------------------------------------------- -`~numpy.ndarray.tobytes` has existed since the 1.9 release, but until this -release `~numpy.ndarray.tostring` emitted no warning. The change to emit a -warning brings NumPy in line with the builtin `array.array` methods of the -same name. - -(`gh-15867 <https://github.com/numpy/numpy/pull/15867>`__) - - Expired deprecations ==================== -`numpy.insert` and `numpy.delete` can no longer be passed an axis on 0d arrays ------------------------------------------------------------------------------- +``numpy.insert`` and ``numpy.delete`` can no longer be passed an axis on 0d arrays +---------------------------------------------------------------------------------- This concludes a deprecation from 1.9, where when an ``axis`` argument was -passed to a call to `~numpy.insert` and `~numpy.delete` on a 0d array, the +passed to a call to ``~numpy.insert`` and ``~numpy.delete`` on a 0d array, the ``axis`` and ``obj`` argument and indices would be completely ignored. In these cases, ``insert(arr, "nonsense", 42, axis=0)`` would actually overwrite the entire array, while ``delete(arr, "nonsense", axis=0)`` would be ``arr.copy()`` -Now passing ``axis`` on a 0d array raises `~numpy.AxisError`. +Now passing ``axis`` on a 0d array raises ``~numpy.AxisError``. (`gh-15802 <https://github.com/numpy/numpy/pull/15802>`__) -`numpy.delete` no longer ignores out-of-bounds indices ------------------------------------------------------- +``numpy.delete`` no longer ignores out-of-bounds indices +-------------------------------------------------------- This concludes deprecations from 1.8 and 1.9, where ``np.delete`` would ignore both negative and out-of-bounds items in a sequence of indices. This was at odds with its behavior when passed a single index. @@ -117,8 +51,8 @@ end. (`gh-15804 <https://github.com/numpy/numpy/pull/15804>`__) -`numpy.insert` and `numpy.delete` no longer accept non-integral indices ------------------------------------------------------------------------ +``numpy.insert`` and ``numpy.delete`` no longer accept non-integral indices +--------------------------------------------------------------------------- This concludes a deprecation from 1.9, where sequences of non-integers indices were allowed and cast to integers. Now passing sequences of non-integral indices raises ``IndexError``, just like it does when passing a single @@ -126,8 +60,8 @@ non-integral scalar. (`gh-15805 <https://github.com/numpy/numpy/pull/15805>`__) -`numpy.delete` no longer casts boolean indices to integers ----------------------------------------------------------- +``numpy.delete`` no longer casts boolean indices to integers +------------------------------------------------------------ This concludes a deprecation from 1.8, where ``np.delete`` would cast boolean arrays and scalars passed as an index argument into integer indices. The behavior now is to treat boolean arrays as a mask, and to raise an error @@ -139,19 +73,18 @@ on boolean scalars. Compatibility notes =================== -Changed random variate stream from `numpy.random.Generator.dirichlet` ---------------------------------------------------------------------- +Changed random variate stream from ``numpy.random.Generator.dirichlet`` +----------------------------------------------------------------------- A bug in the generation of random variates for the Dirichlet distribution -with small `alpha` values was fixed by using a different algorithm when +with small 'alpha' values was fixed by using a different algorithm when ``max(alpha) < 0.1``. Because of the change, the stream of variates -generated by `dirichlet` in this case will be different from previous +generated by ``dirichlet`` in this case will be different from previous releases. (`gh-14924 <https://github.com/numpy/numpy/pull/14924>`__) Scalar promotion in ``PyArray_ConvertToCommonType`` --------------------------------------------------- - The promotion of mixed scalars and arrays in ``PyArray_ConvertToCommonType`` has been changed to adhere to those used by ``np.result_type``. This means that input such as ``(1000, np.array([1], dtype=np.uint8)))`` @@ -171,10 +104,9 @@ will be given. (`gh-14942 <https://github.com/numpy/numpy/pull/14942>`__) -`np.ediff1d` casting behaviour with ``to_end`` and ``to_begin`` ---------------------------------------------------------------- - -`np.ediff1d` now uses the ``"same_kind"`` casting rule for +``np.ediff1d`` casting behaviour with ``to_end`` and ``to_begin`` +----------------------------------------------------------------- +``np.ediff1d`` now uses the ``"same_kind"`` casting rule for its additional ``to_end`` and ``to_begin`` arguments. This ensures type safety except when the input array has a smaller integer type than ``to_begin`` or ``to_end``. @@ -199,7 +131,6 @@ after the first 0). Removed ``multiarray.int_asbuffer`` ----------------------------------- - As part of the continued removal of Python 2 compatibility, ``multiarray.int_asbuffer`` was removed. On Python 3, it threw a ``NotImplementedError`` and was unused internally. It is expected that there @@ -224,8 +155,7 @@ necessary for codebases supporting Python 2.5 and older. ``issubdtype`` no longer interprets ``float`` as ``np.floating`` ---------------------------------------------------------------- - -`numpy.issubdtype` had a FutureWarning since NumPy 1.14 which +``numpy.issubdtype`` had a FutureWarning since NumPy 1.14 which has expired now. This means that certain input where the second argument was neither a datatype nor a NumPy scalar type (such as a string or a python type like ``int`` or ``float``) @@ -239,16 +169,16 @@ Change output of ``round`` on scalars to be consistent with Python ------------------------------------------------------------------ Output of the ``__round__`` dunder method and consequently the Python -built-in `round` has been changed to be a Python `int` to be consistent +built-in ``round`` has been changed to be a Python ``int`` to be consistent with calling it on Python ``float`` objects when called with no arguments. -Previously, it would return a scalar of the `np.dtype` that was passed in. +Previously, it would return a scalar of the ``np.dtype`` that was passed in. (`gh-15840 <https://github.com/numpy/numpy/pull/15840>`__) The ``numpy.ndarray`` constructor no longer interprets ``strides=()`` as ``strides=None`` ----------------------------------------------------------------------------------------- The former has changed to have the expected meaning of setting -`numpy.ndarray.strides` to ``()``, while the latter continues to result in +``numpy.ndarray.strides`` to ``()``, while the latter continues to result in strides being chosen automatically. (`gh-15882 <https://github.com/numpy/numpy/pull/15882>`__) @@ -267,6 +197,93 @@ of users. (`gh-16068 <https://github.com/numpy/numpy/pull/16068>`__) +``SeedSequence`` with small seeds no longer conflicts with spawning +------------------------------------------------------------------- +Small seeds (less than ``2**96``) were previously implicitly 0-padded out to +128 bits, the size of the internal entropy pool. When spawned, the spawn key +was concatenated before the 0-padding. Since the first spawn key is ``(0,)``, +small seeds before the spawn created the same states as the first spawned +``SeedSequence``. Now, the seed is explicitly 0-padded out to the internal +pool size before concatenating the spawn key. Spawned ``SeedSequences`` will +produce different results than in the previous release. Unspawned +``SeedSequences`` will still produce the same results. + +(`gh-16551 <https://github.com/numpy/numpy/pull/16551>`__) + + +Deprecations +============ + +Deprecate automatic ``dtype=object`` for ragged input +----------------------------------------------------- +Calling ``np.array([[1, [1, 2, 3]])`` will issue a ``DeprecationWarning`` as +per `NEP 34`_. Users should explicitly use ``dtype=object`` to avoid the +warning. + +.. _`NEP 34`: https://numpy.org/neps/nep-0034.html + +(`gh-15119 <https://github.com/numpy/numpy/pull/15119>`__) + +Passing ``shape=0`` to factory functions in ``numpy.rec`` is deprecated +----------------------------------------------------------------------- +``0`` is treated as a special case and is aliased to ``None`` in the functions: + +* ``numpy.core.records.fromarrays`` +* ``numpy.core.records.fromrecords`` +* ``numpy.core.records.fromstring`` +* ``numpy.core.records.fromfile`` + +In future, ``0`` will not be special cased, and will be treated as an array +length like any other integer. + +(`gh-15217 <https://github.com/numpy/numpy/pull/15217>`__) + +Deprecation of probably unused C-API functions +---------------------------------------------- +The following C-API functions are probably unused and have been +deprecated: + +* ``PyArray_GetArrayParamsFromObject`` +* ``PyUFunc_GenericFunction`` +* ``PyUFunc_SetUsesArraysAsData`` + +In most cases ``PyArray_GetArrayParamsFromObject`` should be replaced +by converting to an array, while ``PyUFunc_GenericFunction`` can be +replaced with ``PyObject_Call`` (see documentation for details). + +(`gh-15427 <https://github.com/numpy/numpy/pull/15427>`__) + +Converting certain types to dtypes is Deprecated +------------------------------------------------ +The super classes of scalar types, such as ``np.integer``, ``np.generic``, +or ``np.inexact`` will now give a deprecation warning when converted +to a dtype (or used in a dtype keyword argument). +The reason for this is that ``np.integer`` is converted to ``np.int_``, +while it would be expected to represent *any* integer (e.g. also +``int8``, ``int16``, etc. +For example, ``dtype=np.floating`` is currently identical to +``dtype=np.float64``, even though also ``np.float32`` is a subclass of +``np.floating``. + +(`gh-15534 <https://github.com/numpy/numpy/pull/15534>`__) + +Deprecation of ``round`` for ``np.complexfloating`` scalars +----------------------------------------------------------- +Output of the ``__round__`` dunder method and consequently the Python built-in +``round`` has been deprecated on complex scalars. This does not affect +``np.round``. + +(`gh-15840 <https://github.com/numpy/numpy/pull/15840>`__) + +``numpy.ndarray.tostring()`` is deprecated in favor of ``tobytes()`` +-------------------------------------------------------------------- +``~numpy.ndarray.tobytes`` has existed since the 1.9 release, but until this +release ``~numpy.ndarray.tostring`` emitted no warning. The change to emit a +warning brings NumPy in line with the builtin ``array.array`` methods of the +same name. + +(`gh-15867 <https://github.com/numpy/numpy/pull/15867>`__) + C API changes ============= @@ -275,9 +292,9 @@ Better support for ``const`` dimensions in API functions -------------------------------------------------------- The following functions now accept a constant array of ``npy_intp``: -* `PyArray_BroadcastToShape` -* `PyArray_IntTupleFromIntp` -* `PyArray_OverflowMultiplyList` +* ``PyArray_BroadcastToShape`` +* ``PyArray_IntTupleFromIntp`` +* ``PyArray_OverflowMultiplyList`` Previously the caller would have to cast away the const-ness to call these functions. @@ -298,11 +315,11 @@ the compiler warnings or to const qualify their own loop signatures. New Features ============ -`numpy.frompyfunc` now accepts an identity argument ---------------------------------------------------- -This allows the :attr:`numpy.ufunc.identity` attribute to be set on the +``numpy.frompyfunc`` now accepts an identity argument +----------------------------------------------------- +This allows the :attr:``numpy.ufunc.identity`` attribute to be set on the resulting ufunc, meaning it can be used for empty and multi-dimensional -calls to :meth:`numpy.ufunc.reduce`. +calls to :meth:``numpy.ufunc.reduce``. (`gh-8255 <https://github.com/numpy/numpy/pull/8255>`__) @@ -314,40 +331,40 @@ now expose this through the buffer interface, meaning (`gh-15385 <https://github.com/numpy/numpy/pull/15385>`__) -``subok`` option for `numpy.copy` ---------------------------------- -A new kwarg, ``subok``, was added to `numpy.copy` to allow users to toggle the -behavior of `numpy.copy` with respect to array subclasses. The default value -is ``False`` which is consistent with the behavior of `numpy.copy` for +``subok`` option for ``numpy.copy`` +----------------------------------- +A new kwarg, ``subok``, was added to ``numpy.copy`` to allow users to toggle +the behavior of ``numpy.copy`` with respect to array subclasses. The default +value is ``False`` which is consistent with the behavior of ``numpy.copy`` for previous numpy versions. To create a copy that preserves an array subclass with -`numpy.copy`, call ``np.copy(arr, subok=True)``. This addition better documents -that the default behavior of `numpy.copy` differs from the -`numpy.ndarray.copy` method which respects array subclasses by default. +``numpy.copy``, call ``np.copy(arr, subok=True)``. This addition better +documents that the default behavior of ``numpy.copy`` differs from the +``numpy.ndarray.copy`` method which respects array subclasses by default. (`gh-15685 <https://github.com/numpy/numpy/pull/15685>`__) -`numpy.linalg.multi_dot` now accepts an ``out`` argument --------------------------------------------------------- +``numpy.linalg.multi_dot`` now accepts an ``out`` argument +---------------------------------------------------------- ``out`` can be used to avoid creating unnecessary copies of the final product -computed by `numpy.linalg.multidot`. +computed by ``numpy.linalg.multidot``. (`gh-15715 <https://github.com/numpy/numpy/pull/15715>`__) -``keepdims`` parameter for `numpy.count_nonzero` ------------------------------------------------- -The parameter ``keepdims`` was added to `numpy.count_nonzero`. The +``keepdims`` parameter for ``numpy.count_nonzero`` +-------------------------------------------------- +The parameter ``keepdims`` was added to ``numpy.count_nonzero``. The parameter has the same meaning as it does in reduction functions such -as `numpy.sum` or `numpy.mean`. +as ``numpy.sum`` or ``numpy.mean``. (`gh-15870 <https://github.com/numpy/numpy/pull/15870>`__) -``equal_nan`` parameter for `numpy.array_equal` ------------------------------------------------- -The keyword argument ``equal_nan`` was added to `numpy.array_equal`. -``equal_nan`` is a boolean value that toggles whether or not ``nan`` values -are considered equal in comparison (default is ``False``). This matches API -used in related functions such as `numpy.isclose` and `numpy.allclose`. +``equal_nan`` parameter for ``numpy.array_equal`` +------------------------------------------------- +The keyword argument ``equal_nan`` was added to ``numpy.array_equal``. +``equal_nan`` is a boolean value that toggles whether or not ``nan`` values are +considered equal in comparison (default is ``False``). This matches API used in +related functions such as ``numpy.isclose`` and ``numpy.allclose``. (`gh-16128 <https://github.com/numpy/numpy/pull/16128>`__) @@ -357,9 +374,8 @@ Improvements Improve detection of CPU features ================================= - -Replace ``npy_cpu_supports`` which was a gcc-specific mechanism to test support -of avx with more general functions ``npy_cpu_init`` and ``npy_cpu_have``, and +Replace ``npy_cpu_supports`` which was a gcc specific mechanism to test support +of AVX with more general functions ``npy_cpu_init`` and ``npy_cpu_have``, and expose the results via a ``NPY_CPU_HAVE`` c-macro as well as a python-level ``__cpu_features__`` dictionary. @@ -367,7 +383,6 @@ expose the results via a ``NPY_CPU_HAVE`` c-macro as well as a python-level Use 64-bit integer size on 64-bit platforms in fallback lapack_lite ------------------------------------------------------------------- - Use 64-bit integer size on 64-bit platforms in the fallback LAPACK library, which is used when the system has no LAPACK installed, allowing it to deal with linear algebra for large arrays. @@ -375,22 +390,20 @@ linear algebra for large arrays. (`gh-15218 <https://github.com/numpy/numpy/pull/15218>`__) Use AVX512 intrinsic to implement ``np.exp`` when input is ``np.float64`` --------------------------------------------------------------------------- +------------------------------------------------------------------------- Use AVX512 intrinsic to implement ``np.exp`` when input is ``np.float64``, which can improve the performance of ``np.exp`` with ``np.float64`` input 5-7x -faster than before. The _multiarray_umath.so module has grown about 63 KB on -linux64. +faster than before. The ``_multiarray_umath.so`` module has grown about 63 KB +on linux64. (`gh-15648 <https://github.com/numpy/numpy/pull/15648>`__) Ability to disable madvise hugepages ------------------------------------ - -On Linux NumPy has previously added support for madavise -hugepages which can improve performance for very large arrays. -Unfortunately, on older Kernel versions this led to peformance -regressions, thus by default the support has been disabled on -kernels before version 4.6. To override the default, you can +On Linux NumPy has previously added support for madavise hugepages which can +improve performance for very large arrays. Unfortunately, on older Kernel +versions this led to peformance regressions, thus by default the support has +been disabled on kernels before version 4.6. To override the default, you can use the environment variable:: NUMPY_MADVISE_HUGEPAGE=0 @@ -401,17 +414,17 @@ transparent hugepage. (`gh-15769 <https://github.com/numpy/numpy/pull/15769>`__) -`numpy.einsum` accepts NumPy ``int64`` type in subscript list -------------------------------------------------------------- -There is no longer a type error thrown when `numpy.einsum` is passed +``numpy.einsum`` accepts NumPy ``int64`` type in subscript list +--------------------------------------------------------------- +There is no longer a type error thrown when ``numpy.einsum`` is passed a NumPy ``int64`` array as its subscript list. (`gh-16080 <https://github.com/numpy/numpy/pull/16080>`__) ``np.logaddexp2.identity`` changed to ``-inf`` ---------------------------------------------- -The ufunc `~numpy.logaddexp2` now has an identity of ``-inf``, allowing it to -be called on empty sequences. This matches the identity of `~numpy.logaddexp`. +The ufunc ``~numpy.logaddexp2`` now has an identity of ``-inf``, allowing it to +be called on empty sequences. This matches the identity of ``~numpy.logaddexp``. (`gh-16102 <https://github.com/numpy/numpy/pull/16102>`__) @@ -431,31 +444,27 @@ was for its use. It has been removed. ``numpy.random._bit_generator`` moved to ``numpy.random.bit_generator`` ----------------------------------------------------------------------- +In order to expose ``numpy.random.BitGenerator`` and +``numpy.random.SeedSequence`` to Cython, the ``_bitgenerator`` module is now +public as ``numpy.random.bit_generator`` -In order to expose `numpy.random.BitGenerator` and `numpy.random.SeedSequence` -to cython, the ``_bitgenerator`` module is now public as -`numpy.random.bit_generator` - -Cython access to the random distributions is provided via a `pxd` file ----------------------------------------------------------------------- - +Cython access to the random distributions is provided via a ``pxd`` file +------------------------------------------------------------------------ ``c_distributions.pxd`` provides access to the c functions behind many of the random distributions from Cython, making it convenient to use and extend them. (`gh-15463 <https://github.com/numpy/numpy/pull/15463>`__) -`Fixed `eigh` and `cholesky` methods in `numpy.random.multivariate_normal`` ---------------------------------------------------------------------------- - -Previously, when passing `method='eigh'` or `method='cholesky'`, -`numpy.random.multivariate_normal` produced samples from the wrong +Fixed ``eigh`` and ``cholesky`` methods in ``numpy.random.multivariate_normal`` +------------------------------------------------------------------------------- +Previously, when passing ``method='eigh'`` or ``method='cholesky'``, +``numpy.random.multivariate_normal`` produced samples from the wrong distribution. This is now fixed. (`gh-15872 <https://github.com/numpy/numpy/pull/15872>`__) Fixed the jumping implementation in ``MT19937.jumped`` ------------------------------------------------------ - This fix changes the stream produced from jumped MT19937 generators. It does not affect the stream produced using ``RandomState`` or ``MT19937`` that are directly seeded. @@ -466,10 +475,3 @@ implementation of the Horner and Sliding Window jump methods. (`gh-16153 <https://github.com/numpy/numpy/pull/16153>`__) - -.. currentmodule:: numpy - -========================== -NumPy 1.19.0 Release Notes -========================== - diff --git a/doc/source/user/building.rst b/doc/source/user/building.rst index 47a0a03c9..54ece3da3 100644 --- a/doc/source/user/building.rst +++ b/doc/source/user/building.rst @@ -6,6 +6,10 @@ Building from source A general overview of building NumPy from source is given here, with detailed instructions for specific platforms given separately. +.. + This page is referenced from numpy/numpy/__init__.py. Please keep its + location in sync with the link there. + Prerequisites ------------- @@ -27,7 +31,7 @@ Building NumPy requires the following software installed: MSVC and Clang compilers. Compilers from other vendors such as Intel, Absoft, Sun, NAG, Compaq, Vast, Portland, Lahey, HP, IBM are only supported in the form of community feedback, and may not work out of the box. - GCC 4.x (and later) compilers are recommended. + GCC 4.x (and later) compilers are recommended. On ARM64 (aarch64) GCC 8.x (and later) are recommended. 3) Linear Algebra libraries @@ -107,6 +111,8 @@ means that g77 has been used (note: g77 is no longer supported for building NumP If libgfortran.so is a dependency, gfortran has been used. If both are dependencies, this means both have been used, which is almost always a very bad idea. +.. _accelerated-blas-lapack-libraries: + Accelerated BLAS/LAPACK libraries --------------------------------- @@ -159,6 +165,14 @@ will prefer to use ATLAS, then OpenBLAS and as a last resort MKL. If neither of these exists the build will fail (names are compared lower case). +.. deprecated:: 1.20 + The native libraries on macOS, provided by Accelerate, are not fit for use + in NumPy since they have bugs that cause wrong output under easily reproducible + conditions. If the vendor fixes those bugs, the library could be reinstated, + but until then users compiling for themselves should use another linear + algebra library or use the built-in (but slower) default, see the next + section. + Disabling ATLAS and other accelerated libraries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/user/tutorial-ma.rst b/doc/source/user/tutorial-ma.rst new file mode 100644 index 000000000..c28353371 --- /dev/null +++ b/doc/source/user/tutorial-ma.rst @@ -0,0 +1,376 @@ +======================= +Tutorial: Masked Arrays +======================= + +.. currentmodule:: numpy + +.. testsetup:: + + import numpy as np + np.random.seed(1) + +**Prerequisites** + +Before reading this tutorial, you should know a bit of Python. If you +would like to refresh your memory, take a look at the +:doc:`Python tutorial <python:tutorial/index>`. + +If you want to be able to run the examples in this tutorial, you should also +have `matplotlib <https://matplotlib.org/>`_ installed on your computer. + +**Learner profile** + +This tutorial is for people who have a basic understanding of NumPy and want to +understand how masked arrays and the :mod:`numpy.ma` module can be used in +practice. + +**Learning Objectives** + +After this tutorial, you should be able to: + +- Understand what are masked arrays and how they can be created +- Understand how to access and modify data for masked arrays +- Decide when the use of masked arrays is appropriate in some of your + applications + +**What are masked arrays?** + +Consider the following problem. You have a dataset with missing or invalid +entries. If you're doing any kind of processing on this data, and want to +`skip` or flag these unwanted entries without just deleting them, you may have +to use conditionals or filter your data somehow. The :mod:`numpy.ma` module +provides some of the same funcionality of +:class:`NumPy ndarrays <numpy.ndarray>` with added structure to ensure +invalid entries are not used in computation. + +From the :mod:`Reference Guide <numpy.ma>`: + + A masked array is the combination of a standard :class:`numpy.ndarray` and + a **mask**. A mask is either ``nomask``, indicating that no value of the + associated array is invalid, or an array of booleans that determines for + each element of the associated array whether the value is valid or not. + When an element of the mask is ``False``, the corresponding element of the + associated array is valid and is said to be unmasked. When an element of + the mask is ``True``, the corresponding element of the associated array is + said to be masked (invalid). + + +We can think of a :class:`MaskedArray <numpy.ma.MaskedArray>` as a +combination of: + +- Data, as a regular :class:`numpy.ndarray` of any shape or datatype; +- A boolean mask with the same shape as the data; +- A ``fill_value``, a value that may be used to replace the invalid entries + in order to return a standard :class:`numpy.ndarray`. + +**When can they be useful?** + +There are a few situations where masked arrays can be more useful than just +eliminating the invalid entries of an array: + +- When you want to preserve the values you masked for later processing, without + copying the array; +- When you have to handle many arrays, each with their own mask. If the mask is + part of the array, you avoid bugs and the code is possibly more compact; +- When you have different flags for missing or invalid values, and wish to + preserve these flags without replacing them in the original dataset, but + exclude them from computations; +- If you can't avoid or eliminate missing values, but don't want to deal with + :class:`NaN <numpy.nan>` (Not A Number) values in your operations. + +Masked arrays are also a good idea since the :mod:`numpy.ma` module also +comes with a specific implementation of most :term:`NumPy universal functions +(ufuncs) <ufunc>`, which means that you can still apply fast vectorized +functions and operations on masked data. The output is then a masked array. +We'll see some examples of how this works in practice below. + +**Using masked arrays to see COVID-19 data** + +From `Kaggle <https://www.kaggle.com/atilamadai/covid19>`_ it is possible to +download a dataset with initial data about the COVID-19 outbreak in the +beginning of 2020. We are going to look at a small subset of this data, +contained in the file ``who_covid_19_sit_rep_time_series.csv``. + +.. ipython:: python + + import numpy as np + import os + # The os.getcwd() function returns the current folder; you can change + # the filepath variable to point to the folder where you saved the .csv file + filepath = os.getcwd() + @suppress + filepath = os.path.join(filepath, "source", "user") + filename = os.path.join(filepath, "who_covid_19_sit_rep_time_series.csv") + +The data file contains data of different types and is organized as follows: + +- The first row is a header line that (mostly) describes the data in each column + that follow in the rows below, and beginning in the fourth column, the header + is the date of the observation. +- The second through seventh row contain summary data that is of a different + type than that which we are going to examine, so we will need to exclude that + from the data with which we will work. +- The numerical data we wish to work with begins at column 4, row 8, and extends + from there to the rightmost column and the lowermost row. + +Let's explore the data inside this file for the first 14 days of records. To +gather data from the ``.csv`` file, we will use the :func:`numpy.genfromtxt` +function, making sure we select only the columns with actual numbers instead of +the first three columns which contain location data. We also skip the first 7 +rows of this file, since they contain other data we are not interested in. +Separately, we will extract the information about dates and location for this +data. + +.. ipython:: python + + # Note we are using skip_header and usecols to read only portions of the + # data file into each variable. + # Read just the dates for columns 3-7 from the first row + dates = np.genfromtxt(filename, dtype=np.unicode_, delimiter=",", + max_rows=1, usecols=range(3, 17), + encoding="utf-8-sig") + # Read the names of the geographic locations from the first two + # columns, skipping the first seven rows + locations = np.genfromtxt(filename, dtype=np.unicode_, delimiter=",", + skip_header=7, usecols=(0, 1), + encoding="utf-8-sig") + # Read the numeric data from just the first 14 days + nbcases = np.genfromtxt(filename, dtype=np.int_, delimiter=",", + skip_header=7, usecols=range(3, 17), + encoding="utf-8-sig") + +Included in the :func:`numpy.genfromtxt` function call, we have selected the +:class:`numpy.dtype` for each subset of the data (either an integer - +:class:`numpy.int_` - or a string of characters - :class:`numpy.unicode_`). We +have also used the ``encoding`` argument to select ``utf-8-sig`` as the encoding +for the file (read more about encoding in the `official Python documentation +<https://docs.python.org/3/library/codecs.html#encodings-and-unicode>`__). You +can read more about the :func:`numpy.genfromtxt` function from +the :func:`Reference Documentation <numpy.genfromtxt>` or from the +:doc:`Basic IO tutorial <basics.io.genfromtxt>`. + +**Exploring the data** + +First of all, we can plot the whole set of data we have and see what it looks +like. In order to get a readable plot, we select only a few of the dates to +show in our :func:`x-axis ticks <matplotlib.pyplot.xticks>`. Note also that in +our plot command, we use ``nbcases.T`` (the transpose of the ``nbcases`` array) +since this means we will plot each row of the file as a separate line. We choose +to plot a dashed line (using the ``'--'`` line style). See the +`matplotlib <https://matplotlib.org/>`_ documentation for more info on this. + +.. ipython:: python + + import matplotlib.pyplot as plt + selected_dates = [0, 3, 11, 13] + plt.plot(dates, nbcases.T, '--'); + plt.xticks(selected_dates, dates[selected_dates]); + @savefig plot_covid_1.png + plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020"); + +.. note:: + + If you are executing the commands above in the IPython shell, it might be + necessary to use the command ``plt.show()`` to show the image window. Note + also that we use a semicolon at the end of a line to suppress its output, but + this is optional. + +The graph has a strange shape from January 24th to February 1st. It would be +interesing to know where this data comes from. If we look at the ``locations`` +array we extracted from the ``.csv`` file, we can see that we have two columns, +where the first would contain regions and the second would contain the name of +the country. However, only the first few rows contain data for the the first +column (province names in China). Following that, we only have country names. So +it would make sense to group all the data from China into a single row. For +this, we'll select from the ``nbcases`` array only the rows for which the +second entry of the ``locations`` array corresponds to China. Next, we'll use +the :func:`numpy.sum` function to sum all the selected rows (``axis=0``): + +.. ipython:: python + + china_total = nbcases[locations[:, 1] == 'China'].sum(axis=0) + china_total + +Something's wrong with this data - we are not supposed to have negative values +in a cumulative data set. What's going on? + +**Missing data** + +Looking at the data, here's what we find: there is a period with +**missing data**: + +.. ipython:: python + + nbcases + +All the ``-1`` values we are seeing come from :func:`numpy.genfromtxt` +attempting to read missing data from the original ``.csv`` file. Obviously, we +don't want to compute missing data as ``-1`` - we just want to skip this value +so it doesn't interfere in our analysis. After importing the :mod:`numpy.ma` +module, we'll create a new array, this time masking the invalid values: + +.. ipython:: python + + from numpy import ma + nbcases_ma = ma.masked_values(nbcases, -1) + +If we look at the ``nbcases_ma`` masked array, this is what we have: + +.. ipython:: python + + nbcases_ma + +We can see that this is a different kind of array. As mentioned in the +introduction, it has three attributes (``data``, ``mask`` and ``fill_value``). +Keep in mind that the ``mask`` attribute has a ``True`` value for elements +corresponding to **invalid** data (represented by two dashes in the ``data`` +attribute). + +.. note:: + + Adding ``-1`` to missing data is not a problem with :func:`numpy.genfromtxt`; + in this particular case, substituting the missing value with ``0`` might have + been fine, but we'll see later that this is far from a general solution. + Also, it is possible to call the :func:`numpy.genfromtxt` function using the + ``usemask`` parameter. If ``usemask=True``, :func:`numpy.genfromtxt` + automatically returns a masked array. + +Let's try and see what the data looks like excluding the first row +(data from the Hubei province in China) so we can look at the missing data more +closely: + +.. ipython:: python + + plt.plot(dates, nbcases_ma[1:].T, '--'); + plt.xticks(selected_dates, dates[selected_dates]); + @savefig plot_covid_2.png + plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020"); + +Now that our data has been masked, let's try summing up all the cases in China: + +.. ipython:: python + + china_masked = nbcases_ma[locations[:, 1] == 'China'].sum(axis=0) + china_masked + +Note that ``china_masked`` is a masked array, so it has a different data +structure than a regular NumPy array. Now, we can access its data directly by +using the ``.data`` attribute: + +.. ipython:: python + + china_total = china_masked.data + china_total + +That is better: no more negative values. However, we can still see that for some +days, the cumulative number of cases seems to go down (from 835 to 10, for +example), which does not agree with the definition of "cumulative data". If we +look more closely at the data, we can see that in the period where there was +missing data in mainland China, there was valid data for Hong Kong, Taiwan, +Macau and "Unspecified" regions of China. Maybe we can remove those from the +total sum of cases in China, to get a better understanding of the data. + +First, we'll identify the indices of locations in mainland China: + +.. ipython:: python + + china_mask = ((locations[:, 1] == 'China') & + (locations[:, 0] != 'Hong Kong') & + (locations[:, 0] != 'Taiwan') & + (locations[:, 0] != 'Macau') & + (locations[:, 0] != 'Unspecified*')) + +Now, ``china_mask`` is an array of boolean values (``True`` or ``False``); we +can check that the indices are what we wanted with the :func:`ma.nonzero` method +for masked arrays: + +.. ipython:: python + + china_mask.nonzero() + +Now we can correctly sum entries for mainland China: + +.. ipython:: python + + china_total = nbcases_ma[china_mask].sum(axis=0) + china_total + +We can replace the data with this information and plot a new graph, focusing on +Mainland China: + +.. ipython:: python + + plt.plot(dates, china_total.T, '--'); + plt.xticks(selected_dates, dates[selected_dates]); + @savefig plot_covid_3.png + plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020 - Mainland China"); + +It's clear that masked arrays are the right solution here. We cannot represent +the missing data without mischaracterizing the evolution of the curve. + +**Fitting Data** + +One possibility we can think of is to interpolate the missing data to estimate +the number of cases in late January. Observe that we can select the masked +elements using the ``.mask`` attribute: + +.. ipython:: python + + china_total.mask + invalid = china_total[china_total.mask] + invalid + +We can also access the valid entries by using the logical negation for this +mask: + +.. ipython:: python + + valid = china_total[~china_total.mask] + valid + +Now, if we want to create a very simple approximation for this data, we should +take into account the valid entries around the invalid ones. So first let's +select the dates for which the data is valid. Note that we can use the mask +from the ``china_total`` masked array to index the dates array: + +.. ipython:: python + + dates[~china_total.mask] + +Finally, we can use the :func:`numpy.polyfit` and :func:`numpy.polyval` +functions to create a cubic polynomial that fits the data as best as possible: + +.. ipython:: python + + t = np.arange(len(china_total)) + params = np.polyfit(t[~china_total.mask], valid, 3) + cubic_fit = np.polyval(params, t) + plt.plot(t, china_total); + @savefig plot_covid_4.png + plt.plot(t, cubic_fit, '--'); + +This plot is not so readable since the lines seem to be over each other, so +let's summarize in a more elaborate plot. We'll plot the real data when +available, and show the cubic fit for unavailable data, using this fit to +compute an estimate to the observed number of cases on January 28th 2020, 7 days +after the beginning of the records: + +.. ipython:: python + + plt.plot(t, china_total); + plt.plot(t[china_total.mask], cubic_fit[china_total.mask], '--', color='orange'); + plt.plot(7, np.polyval(params, 7), 'r*'); + plt.xticks([0, 7, 13], dates[[0, 7, 13]]); + plt.yticks([0, np.polyval(params, 7), 10000, 17500]); + plt.legend(['Mainland China', 'Cubic estimate', '7 days after start']); + @savefig plot_covid_5.png + plt.title("COVID-19 cumulative cases from Jan 21 to Feb 3 2020 - Mainland China\n" + "Cubic estimate for 7 days after start"); + +**More reading** + +Topics not covered in this tutorial can be found in the documentation: + +- :func:`Hardmasks <numpy.ma.harden_mask>` vs. :func:`softmasks + <numpy.ma.soften_mask>` +- :ref:`The numpy.ma module <maskedarray.generic>` diff --git a/doc/source/user/tutorials_index.rst b/doc/source/user/tutorials_index.rst index c5e859fad..5e9419f96 100644 --- a/doc/source/user/tutorials_index.rst +++ b/doc/source/user/tutorials_index.rst @@ -15,5 +15,6 @@ classes contained in the package, see the :ref:`API reference <reference>`. misc numpy-for-matlab-users tutorial-svd + tutorial-ma building c-info diff --git a/doc/source/user/who_covid_19_sit_rep_time_series.csv b/doc/source/user/who_covid_19_sit_rep_time_series.csv new file mode 100644 index 000000000..ebf670b8c --- /dev/null +++ b/doc/source/user/who_covid_19_sit_rep_time_series.csv @@ -0,0 +1,115 @@ +Province/States,Country/Region,WHO region,1/21/20,1/22/20,1/23/20,1/24/20,1/25/20,1/26/20,1/27/20,1/28/20,1/29/20,1/30/20,1/31/20,2/1/20,2/2/20,2/3/20,2/4/20,2/5/20,2/6/20,2/7/20,2/8/20,2/9/20,2/10/20,2/11/20,2/12/20,2/13/20,2/14/20,2/15/20,2/16/20,2/17/20,2/18/20,2/19/20,2/20/20,2/21/20,2/22/20,2/23/20,2/24/20,2/25/20,2/26/20,2/27/20,2/28/20,2/29/20,3/1/20,3/2/20,3/3/20
+Confirmed,Globally,,282,314,581,846,1320,2014,2798,4593,6065,7818,9826,11953,14557,17391,20630,24554,28276,31481,34886,37558,40554,43103,45171,46997,49053,50580,51857,71429,73332,75204,75748,76769,77794,78811,79331,80239,81109,82294,83652,85403,87137,88948,90870
+Confirmed,Mainland China,Western Pacific Region,278,309,571,830,1297,1985,2741,4537,5997,7736,9720,11821,14411,17238,20471,24363,28060,31211,34598,37251,40235,42708,44730,46550,48548,50054,51174,70635,72528,74280,74675,75569,76392,77042,77262,77780,78191,78630,78961,79394,79968,80174,80304
+Confirmed,Outside of China,,4,5,10,16,23,29,57,56,68,82,106,132,146,153,159,191,216,270,288,307,319,395,441,447,505,526,683,794,804,924,1073,1200,1402,1769,2069,2459,2918,3664,4691,6009,7169,8774,10566
+Suspected,Mainland China,Western Pacific Region,,,,,,,5794,6973,9239,12167,15238,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+Severe,Mainland China,Western Pacific Region,,,,,,,461,976,1239,1370,1527,1795,2110,2296,2788,3219,3859,4821,6101,6188,6484,7333,8204,,,,,,,,,,,,,,,,,,,,
+Deaths,Mainland China,Western Pacific Region,,,,,,,80,106,132,170,213,259,304,361,425,491,564,637,723,812,909,1017,1114,1260,1381,1524,1666,1772,1870,2006,2121,2239,2348,2445,2595,2666,2718,2747,2791,2838,2873,2915,2946
+Hubei ,China,Western Pacific Region,258,270,375,375,,,,,,,,7153,9074,11177,13522,16678,19665,22112,24953,27100,29631,31728,33366,34874,51968,54406,56249,58182,59989,61682,62031,62662,63454,64084,64287,64786,65187,65596,65914,66337,66907,67103,67217
+Guangdong,China,Western Pacific Region,14,17,26,32,,,,,,,,520,604,683,797,870,944,1018,1075,1120,1151,1177,1219,1241,1261,1295,1316,1322,1328,1331,1332,1333,1339,1342,1345,1347,1347,1347,1348,1349,1349,1350,1350
+Henan,China,Western Pacific Region,,1,1,1,,,,,,,,422,493,566,675,764,851,914,981,1033,1073,1105,1135,1169,1184,1212,1231,1246,1257,1262,1265,1267,1270,1271,1271,1271,1271,1272,1272,1272,1272,1272,1272
+Zhejiang,China,Western Pacific Region,,5,5,5,,,,,,,,599,661,724,829,895,954,1006,1048,1075,1104,1117,1131,1145,1155,1162,1167,1171,1172,1173,1175,1203,1205,1205,1205,1205,1205,1205,1205,1205,1205,1206,1213
+Hunan,China,Western Pacific Region,,1,1,1,,,,,,,,389,463,521,593,661,711,772,803,838,879,912,946,968,988,1001,1004,1006,1007,1008,1010,1011,1013,1016,1016,1016,1016,1017,1017,1018,1018,1018,1018
+Anhui,China,Western Pacific Region,,,,,,,,,,,,297,340,408,480,530,591,665,733,779,830,860,889,910,934,950,962,973,982,986,987,988,989,989,989,989,989,989,990,990,990,990,990
+Jiangxi,China,Western Pacific Region,,1,2,2,,,,,,,,286,333,391,476,548,600,661,698,740,771,804,844,872,900,913,925,930,933,934,934,934,934,934,934,934,934,934,935,935,935,935,935
+Shandong,China,Western Pacific Region,,1,1,1,,,,,,,,202,225,246,270,298,343,379,407,435,459,486,497,506,519,530,537,541,543,544,546,748,750,754,755,755,756,756,756,756,756,758,758
+Jiangsu,China,Western Pacific Region,,,,,,,,,,,,202,231,271,308,341,373,408,439,468,492,515,543,570,593,604,617,626,629,631,631,631,631,631,631,631,631,631,631,631,631,631,631
+Chongqing,China,Western Pacific Region,,1,5,5,,,,,,,,238,262,300,337,366,389,411,426,446,468,486,505,518,529,537,544,551,553,555,560,567,572,573,575,576,576,576,576,576,576,576,576
+Sichuan,China,Western Pacific Region,,1,2,2,,,,,,,,207,236,254,282,301,321,344,363,386,405,417,436,451,463,470,481,495,508,514,520,525,526,526,527,529,531,534,538,538,538,538,538
+Heilongjiang,China,Western Pacific Region,,,,,,,,,,,,80,95,118,155,190,227,277,282,307,331,360,378,395,418,425,445,457,464,470,476,479,479,480,480,480,480,480,480,480,480,480,480
+Beijing,China,Western Pacific Region,5,5,10,10,,,,,,,,156,183,212,228,253,274,297,315,326,337,342,352,366,372,375,380,381,387,393,395,396,399,399,399,400,400,410,410,411,413,414,414
+Shanghai,China,Western Pacific Region,1,2,9,9,,,,,,,,153,177,193,208,233,254,269,281,292,295,302,306,313,318,326,328,331,333,333,333,334,334,335,335,335,336,337,337,337,337,337,338
+Hebei,China,Western Pacific Region,,,,,,,,,,,,96,104,113,126,135,157,171,195,206,218,239,251,265,283,291,300,301,302,306,307,308,309,311,311,311,312,317,318,318,318,318,318
+Fujian,China,Western Pacific Region,,,,,,,,,,,,144,159,179,194,205,215,224,239,250,261,267,272,279,281,285,287,290,292,293,293,293,293,293,293,294,294,296,296,296,296,296,296
+Guangxi,China,Western Pacific Region,,,,,,,,,,,,100,111,127,139,150,168,172,183,195,210,215,222,222,226,235,237,238,242,244,245,246,249,249,251,252,252,252,252,252,252,252,252
+Shaanxi,China,Western Pacific Region,,,,,,,,,,,,101,116,128,142,165,173,184,195,208,213,219,225,229,230,232,236,240,240,242,245,245,245,245,245,245,245,245,245,245,245,245,245
+Yunnan,China,Western Pacific Region,,1,1,1,,,,,,,,91,99,109,117,122,128,135,138,140,141,149,154,155,162,168,169,171,172,172,172,174,174,174,174,174,174,174,174,174,174,174,174
+Hainan,China,Western Pacific Region,,,,,,,,,,,,57,63,70,79,89,100,111,123,128,136,142,145,157,157,162,162,162,163,163,168,168,168,168,168,168,168,168,168,168,168,168,168
+Guizhou,China,Western Pacific Region,,,,,,,,,,,,29,38,46,56,64,69,77,89,96,109,118,131,135,140,143,144,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146,146
+Tianjin,China,Western Pacific Region,,2,2,2,,,,,,,,34,40,49,63,67,70,94,81,88,91,96,106,112,119,120,122,124,125,128,130,131,133,135,135,135,135,135,136,136,136,136,136
+Shanxi,China,Western Pacific Region,,,,,,,,,,,,47,56,66,74,81,90,96,104,115,119,122,124,126,126,127,128,129,130,131,131,132,132,132,132,133,133,133,133,133,133,133,133
+Liaoning,China,Western Pacific Region,,,,,,,,,,,,60,64,70,74,81,89,94,99,105,107,108,111,116,117,119,120,121,121,121,121,121,121,121,121,121,121,121,121,121,122,122,125
+Hong Kong,China,Western Pacific Region,,,1,2,5,5,8,8,8,10,12,13,14,15,15,18,21,24,26,26,36,42,49,50,53,56,56,57,60,62,65,68,68,70,74,81,85,91,93,94,95,98,101
+Jilin,China,Western Pacific Region,,,,,,,,,,,,17,21,31,42,54,59,65,69,78,80,81,83,84,86,88,88,89,89,90,91,91,91,91,93,93,93,93,93,93,93,93,93
+Gansu,China,Western Pacific Region,,,,,,,,,,,,35,45,51,56,57,62,70,71,81,85,86,86,87,90,90,90,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91,91
+Xinjiang,China,Western Pacific Region,,,,,,,,,,,,18,23,24,29,32,36,39,42,45,49,55,59,63,65,70,71,73,76,76,76,76,76,75,76,76,76,76,76,76,76,76,76
+Inner Mongolia,China,Western Pacific Region,,,,,,,,,,,,23,26,33,37,42,46,49,50,54,58,58,60,61,63,68,70,72,73,75,75,75,75,75,75,75,75,75,75,75,75,75,75
+Ningxia,China,Western Pacific Region,,,,,,,,,,,,26,28,31,34,34,40,43,45,45,49,53,58,64,67,70,70,70,70,71,71,71,71,71,71,71,71,72,72,73,73,74,74
+Taiwan,China,Western Pacific Region,,1,1,1,3,3,4,7,8,8,9,10,10,10,10,11,11,16,16,17,18,18,18,18,18,18,18,20,22,23,24,26,26,23,28,31,32,32,34,39,39,40,42
+Qinghai,China,Western Pacific Region,,,,,,,,,,,,8,9,13,15,17,18,18,18,18,18,18,18,18,18,18,18,18,18,12,18,18,18,18,18,18,18,18,18,18,18,18,18
+Macau,China,Western Pacific Region,,,1,2,2,2,5,7,7,7,7,7,7,8,8,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10
+Xizang,China,Western Pacific Region,,,,,,,,,,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
+Unspecified*,China,Western Pacific Region,,,131,384,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+,Japan,Western Pacific Region,1,1,1,1,3,3,4,6,7,11,14,17,20,20,20,33,25,25,25,26,26,26,28,29,33,41,53,59,65,73,85,93,105,132,144,157,164,186,210,230,239,254,268
+,Republic of Korea,Western Pacific Region,1,1,1,2,2,2,4,4,4,4,11,12,15,15,16,18,23,24,24,27,27,28,28,28,28,28,29,30,31,51,104,204,346,602,763,977,1261,1766,2337,3150,3736,4212,4812
+,Thailand,South-East Asia Region,2,2,2,4,4,5,5,14,14,14,14,19,19,19,19,25,25,25,32,32,32,33,33,33,33,34,34,35,35,35,35,35,35,35,35,37,40,40,40,42,42,42,43
+,United States of America,Region of the Americas,,,1,1,2,2,5,5,5,5,6,7,8,11,11,11,12,12,12,12,12,13,13,14,15,15,15,15,15,15,15,15,35,35,35,53,53,59,59,62,62,62,64
+,Vietnam,Western Pacific Region,,,,2,2,2,2,2,2,2,5,6,7,8,9,10,10,12,13,14,14,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16
+,Singapore,Western Pacific Region,,,,1,3,3,4,7,7,10,13,16,18,18,18,24,28,30,33,40,43,45,47,50,58,67,72,75,77,81,84,85,86,89,89,90,91,93,96,98,102,106,108
+,Italy,European Region,,,,,,,,,,,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,9,76,124,229,322,400,650,888,1128,1689,2036
+,Nepal,South-East Asia Region,,,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
+,Australia,Western Pacific Region,,,,,3,3,4,5,7,7,9,12,12,12,12,13,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,17,21,21,21,22,23,23,23,24,25,27,33
+,Malaysia,Western Pacific Region,,,,,,3,4,4,4,7,8,8,8,8,10,10,12,14,15,17,18,18,18,18,19,21,22,22,22,22,22,22,22,22,22,22,22,22,24,24,24,24,29
+,Canada,Region of the Americas,,,,,,,1,2,3,3,3,4,4,4,4,5,5,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,9,9,10,10,11,11,14,19,19,27
+,Cambodia,Western Pacific Region,,,,,,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
+,France,European Region,,,,,3,3,3,3,4,5,6,6,6,6,6,6,6,6,6,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,18,38,57,100,100,191
+,Sri Lanka,South-East Asia Region,,,,,,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
+,Iran,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,5,18,28,43,61,95,141,245,388,593,978,1501
+,India,South-East Asia Region,,,,,,,,,,1,1,1,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,5
+,Germany,European Region,,,,,,,,1,4,4,5,7,8,10,12,12,12,13,14,14,14,14,16,16,16,16,16,16,16,16,16,16,16,16,16,16,18,21,26,57,57,129,157
+,Philippines,Western Pacific Region,,,,,,,,,,1,1,1,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
+,Spain,European Region,,,,,,,,,,,,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,12,25,32,45,45,114
+,United Kingdom,European Region,,,,,,,,,,,,2,2,2,2,2,2,3,3,3,4,8,8,9,9,9,9,9,9,9,9,9,9,9,9,13,13,13,16,20,23,36,39
+,Sweden,European Region,,,,,,,,,,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,7,12,13,14,15
+,Switzerland,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,6,10,18,26,30
+,Austria,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,2,4,5,10,10,18
+,Norway,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,4,6,15,19,25
+,Kuwait,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,8,12,43,43,45,45,56,56
+,Bahrain,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,26,33,33,38,40,47,49
+,United Arab Emirates,Eastern Mediterranean Region,,,,,,,,,4,4,4,4,5,5,5,5,5,5,7,7,7,8,8,8,8,8,8,9,9,9,9,9,11,13,13,13,13,13,19,19,19,21,21
+,Israel,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,2,2,2,3,5,7,7,10
+,Iraq,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,5,6,7,8,13,19,26
+,Oman,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,4,4,6,6,6,6,6
+,Lebanon,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,1,2,2,2,2,10,13
+,Pakistan,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,2,2,4,4,5
+,Egypt,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2
+,Croatia,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,3,3,5,7,7,9
+,Greece,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,3,3,3,7,7
+,Finland,European Region,,,,,,,,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,6,7
+,Algeria,African Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,1,1,5
+,Brazil,Region of the Americas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,2,2,2
+,Russian,European Region,,,,,,,,,,,,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3
+,Belgium,European Region,,,,,,,,,,,,,,,,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,8
+,Denmark,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,2,3,4,5
+,Estonia,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,1,1
+,Georgia,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,2,3,3,3
+,North Macedonia,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,1,1
+,Romania,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,3,3,3,3
+,Afghanistan,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,1,1,1,1
+,New Zealand,Western Pacific Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,2
+,Belarus,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,1
+,Lithuania,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,1
+,Netherlands,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,2,7,13,18
+,Nigeria,African Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,1,1
+,Mexico,Region of the Americas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,2,5,5
+,San Marino,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1,8
+,Azerbaijan,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,3,3
+,Ireland,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1
+,Monaco,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,1
+,Qatar,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,3,7
+,Ecuador,Region of the Americas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,6
+,Czechia,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,3
+,Iceland,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,9
+,Armenia,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1
+,Luxembourg,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1
+,Indonesia,South-East Asia Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,2
+,Dominican Republic,Region of the Americas,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1
+,Portugal,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2
+,Andorra,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1
+,Latvia,European Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1
+,Jordan,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1
+,Morocco,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1
+,Saudi Arabia,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1
+,Tunisia,Eastern Mediterranean Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1
+,Senegal,African Region,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1
+Case on an international conveyance,Other,Other,,,,,,,,,,,,,,,,,20,61,64,64,70,135,175,174,218,218,355,454,454,542,621,634,634,634,695,691,691,705,705,705,706,706,706
\ No newline at end of file diff --git a/doc_requirements.txt b/doc_requirements.txt index 52b51cd6e..6947ec18f 100644 --- a/doc_requirements.txt +++ b/doc_requirements.txt @@ -2,3 +2,4 @@ sphinx>=2.2.0,<3.0 ipython scipy matplotlib +pandas diff --git a/numpy/__init__.pxd b/numpy/__init__.pxd index 338da3d7e..33d90c496 100644 --- a/numpy/__init__.pxd +++ b/numpy/__init__.pxd @@ -608,7 +608,7 @@ cdef extern from "numpy/arrayobject.h": object PyArray_Choose (ndarray, object, ndarray, NPY_CLIPMODE) int PyArray_Sort (ndarray, int, NPY_SORTKIND) object PyArray_ArgSort (ndarray, int, NPY_SORTKIND) - object PyArray_SearchSorted (ndarray, object, NPY_SEARCHSIDE, object) + object PyArray_SearchSorted (ndarray, object, NPY_SEARCHSIDE, PyObject *) object PyArray_ArgMax (ndarray, int, ndarray) object PyArray_ArgMin (ndarray, int, ndarray) object PyArray_Reshape (ndarray, object) diff --git a/numpy/__init__.py b/numpy/__init__.py index e6a24f0d1..0fe8818ef 100644 --- a/numpy/__init__.py +++ b/numpy/__init__.py @@ -136,6 +136,9 @@ else: __all__ = ['ModuleDeprecationWarning', 'VisibleDeprecationWarning'] + # mapping of {name: (value, deprecation_msg)} + __deprecated_attrs__ = {} + # Allow distributors to run custom init code from . import _distributor_init @@ -156,11 +159,35 @@ else: from . import matrixlib as _mat from .matrixlib import * - # Make these accessible from numpy name-space - # but not imported in from numpy import * - # TODO[gh-6103]: Deprecate these - from builtins import bool, int, float, complex, object, str - from .compat import long, unicode + # Deprecations introduced in NumPy 1.20.0, 2020-06-06 + import builtins as _builtins + __deprecated_attrs__.update({ + n: ( + getattr(_builtins, n), + "`np.{n}` is a deprecated alias for the builtin `{n}`. " + "Use `{n}` by itself, which is identical in behavior, to silence " + "this warning. " + "If you specifically wanted the numpy scalar type, use `np.{n}_` " + "here." + .format(n=n) + ) + for n in ["bool", "int", "float", "complex", "object", "str"] + }) + __deprecated_attrs__.update({ + n: ( + getattr(compat, n), + "`np.{n}` is a deprecated alias for `np.compat.{n}`. " + "Use `np.compat.{n}` by itself, which is identical in behavior, " + "to silence this warning. " + "In the likely event your code does not need to work on Python 2 " + "you can use the builtin ``{n2}`` for which ``np.compat.{n}`` is " + "itself an alias. " + "If you specifically wanted the numpy scalar type, use `np.{n2}_` " + "here." + .format(n=n, n2=n2) + ) + for n, n2 in [("long", "int"), ("unicode", "str")] + }) from .core import round, abs, max, min # now that numpy modules are imported, can initialize limits @@ -172,8 +199,10 @@ else: __all__.extend(lib.__all__) __all__.extend(['linalg', 'fft', 'random', 'ctypeslib', 'ma']) - # These are added by `from .core import *` and `core.__all__`, but we - # overwrite them above with builtins we do _not_ want to export. + # These are exported by np.core, but are replaced by the builtins below + # remove them to ensure that we don't end up with `np.long == np.int_`, + # which would be a breaking change. + del long, unicode __all__.remove('long') __all__.remove('unicode') @@ -196,25 +225,33 @@ else: numarray = 'removed' if sys.version_info[:2] >= (3, 7): - # Importing Tester requires importing all of UnitTest which is not a - # cheap import Since it is mainly used in test suits, we lazy import it - # here to save on the order of 10 ms of import time for most users - # - # The previous way Tester was imported also had a side effect of adding - # the full `numpy.testing` namespace - # # module level getattr is only supported in 3.7 onwards # https://www.python.org/dev/peps/pep-0562/ def __getattr__(attr): + # Emit warnings for deprecated attributes + try: + val, msg = __deprecated_attrs__[attr] + except KeyError: + pass + else: + warnings.warn(msg, DeprecationWarning, stacklevel=2) + return val + + # Importing Tester requires importing all of UnitTest which is not a + # cheap import Since it is mainly used in test suits, we lazy import it + # here to save on the order of 10 ms of import time for most users + # + # The previous way Tester was imported also had a side effect of adding + # the full `numpy.testing` namespace if attr == 'testing': import numpy.testing as testing return testing elif attr == 'Tester': from .testing import Tester return Tester - else: - raise AttributeError("module {!r} has no attribute " - "{!r}".format(__name__, attr)) + + raise AttributeError("module {!r} has no attribute " + "{!r}".format(__name__, attr)) def __dir__(): return list(globals().keys() | {'Tester', 'testing'}) @@ -224,6 +261,13 @@ else: # no-one else in the world is using it (though I hope not) from .testing import Tester + # We weren't able to emit a warning about these, so keep them around + globals().update({ + k: v + for k, (v, msg) in __deprecated_attrs__.items() + }) + + # Pytest testing from numpy._pytesttester import PytestTester test = PytestTester(__name__) @@ -279,12 +323,11 @@ else: error_message = "{}: {}".format(w[-1].category.__name__, str(w[-1].message)) msg = ( "Polyfit sanity test emitted a warning, most likely due " - "to using a buggy Accelerate backend. " - "If you compiled yourself, " - "see site.cfg.example for information. " + "to using a buggy Accelerate backend. If you compiled " + "yourself, more information is available at " + "https://numpy.org/doc/stable/user/building.html#accelerated-blas-lapack-libraries " "Otherwise report this to the vendor " - "that provided NumPy.\n{}\n".format( - error_message)) + "that provided NumPy.\n{}\n".format(error_message)) raise RuntimeError(msg) del _mac_os_check @@ -295,11 +338,18 @@ else: import os use_hugepage = os.environ.get("NUMPY_MADVISE_HUGEPAGE", None) if sys.platform == "linux" and use_hugepage is None: - use_hugepage = 1 - kernel_version = os.uname().release.split(".")[:2] - kernel_version = tuple(int(v) for v in kernel_version) - if kernel_version < (4, 6): - use_hugepage = 0 + # If there is an issue with parsing the kernel version, + # set use_hugepages to 0. Usage of LooseVersion will handle + # the kernel version parsing better, but avoided since it + # will increase the import time. See: #16679 for related discussion. + try: + use_hugepage = 1 + kernel_version = os.uname().release.split(".")[:2] + kernel_version = tuple(int(v) for v in kernel_version) + if kernel_version < (4, 6): + use_hugepage = 0 + except ValueError: + use_hugepages = 0 elif use_hugepage is None: # This is not Linux, so it should not matter, just enable anyway use_hugepage = 1 diff --git a/numpy/__init__.pyi b/numpy/__init__.pyi new file mode 100644 index 000000000..ff2cc565e --- /dev/null +++ b/numpy/__init__.pyi @@ -0,0 +1,1237 @@ +import builtins +import sys +import datetime as dt +from abc import abstractmethod + +from numpy.core._internal import _ctypes +from numpy.typing import ArrayLike, DtypeLike, _Shape, _ShapeLike + +from typing import ( + Any, + ByteString, + Callable, + Container, + Callable, + Dict, + Generic, + IO, + Iterable, + List, + Mapping, + Optional, + overload, + Sequence, + Sized, + SupportsAbs, + SupportsComplex, + SupportsFloat, + SupportsInt, + Text, + Tuple, + Type, + TypeVar, + Union, +) + +if sys.version_info[0] < 3: + class SupportsBytes: ... + +else: + from typing import SupportsBytes + +if sys.version_info >= (3, 8): + from typing import Literal, Protocol +else: + from typing_extensions import Literal, Protocol + +# TODO: remove when the full numpy namespace is defined +def __getattr__(name: str) -> Any: ... + +_NdArraySubClass = TypeVar("_NdArraySubClass", bound=ndarray) + +class dtype: + names: Optional[Tuple[str, ...]] + def __init__( + self, + dtype: DtypeLike, + align: bool = ..., + copy: bool = ..., + ) -> None: ... + def __eq__(self, other: DtypeLike) -> bool: ... + def __ne__(self, other: DtypeLike) -> bool: ... + def __gt__(self, other: DtypeLike) -> bool: ... + def __ge__(self, other: DtypeLike) -> bool: ... + def __lt__(self, other: DtypeLike) -> bool: ... + def __le__(self, other: DtypeLike) -> bool: ... + @property + def alignment(self) -> int: ... + @property + def base(self) -> dtype: ... + @property + def byteorder(self) -> str: ... + @property + def char(self) -> str: ... + @property + def descr(self) -> List[Union[Tuple[str, str], Tuple[str, str, _Shape]]]: ... + @property + def fields( + self, + ) -> Optional[Mapping[str, Union[Tuple[dtype, int], Tuple[dtype, int, Any]]]]: ... + @property + def flags(self) -> int: ... + @property + def hasobject(self) -> bool: ... + @property + def isbuiltin(self) -> int: ... + @property + def isnative(self) -> bool: ... + @property + def isalignedstruct(self) -> bool: ... + @property + def itemsize(self) -> int: ... + @property + def kind(self) -> str: ... + @property + def metadata(self) -> Optional[Mapping[str, Any]]: ... + @property + def name(self) -> str: ... + @property + def num(self) -> int: ... + @property + def shape(self) -> _Shape: ... + @property + def ndim(self) -> int: ... + @property + def subdtype(self) -> Optional[Tuple[dtype, _Shape]]: ... + def newbyteorder(self, new_order: str = ...) -> dtype: ... + # Leave str and type for end to avoid having to use `builtins.str` + # everywhere. See https://github.com/python/mypy/issues/3775 + @property + def str(self) -> builtins.str: ... + @property + def type(self) -> Type[generic]: ... + +_Dtype = dtype # to avoid name conflicts with ndarray.dtype + +class _flagsobj: + aligned: bool + updateifcopy: bool + writeable: bool + writebackifcopy: bool + @property + def behaved(self) -> bool: ... + @property + def c_contiguous(self) -> bool: ... + @property + def carray(self) -> bool: ... + @property + def contiguous(self) -> bool: ... + @property + def f_contiguous(self) -> bool: ... + @property + def farray(self) -> bool: ... + @property + def fnc(self) -> bool: ... + @property + def forc(self) -> bool: ... + @property + def fortran(self) -> bool: ... + @property + def num(self) -> int: ... + @property + def owndata(self) -> bool: ... + def __getitem__(self, key: str) -> bool: ... + def __setitem__(self, key: str, value: bool) -> None: ... + +_FlatIterSelf = TypeVar("_FlatIterSelf", bound=flatiter) + +class flatiter(Generic[_ArraySelf]): + @property + def base(self) -> _ArraySelf: ... + @property + def coords(self) -> _Shape: ... + @property + def index(self) -> int: ... + def copy(self) -> _ArraySelf: ... + def __iter__(self: _FlatIterSelf) -> _FlatIterSelf: ... + def __next__(self) -> generic: ... + +_ArraySelf = TypeVar("_ArraySelf", bound=_ArrayOrScalarCommon) + +class _ArrayOrScalarCommon( + SupportsInt, SupportsFloat, SupportsComplex, SupportsBytes, SupportsAbs[Any] +): + @property + def T(self: _ArraySelf) -> _ArraySelf: ... + @property + def base(self) -> Optional[ndarray]: ... + @property + def dtype(self) -> _Dtype: ... + @property + def data(self) -> memoryview: ... + @property + def flags(self) -> _flagsobj: ... + @property + def size(self) -> int: ... + @property + def itemsize(self) -> int: ... + @property + def nbytes(self) -> int: ... + @property + def ndim(self) -> int: ... + @property + def shape(self) -> _Shape: ... + @property + def strides(self) -> _Shape: ... + def __array__(self, __dtype: DtypeLike = ...) -> ndarray: ... + def __int__(self) -> int: ... + def __float__(self) -> float: ... + def __complex__(self) -> complex: ... + if sys.version_info[0] < 3: + def __oct__(self) -> str: ... + def __hex__(self) -> str: ... + def __nonzero__(self) -> bool: ... + def __unicode__(self) -> Text: ... + else: + def __bool__(self) -> bool: ... + def __bytes__(self) -> bytes: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __copy__(self: _ArraySelf, order: str = ...) -> _ArraySelf: ... + def __deepcopy__(self: _ArraySelf, memo: dict) -> _ArraySelf: ... + def __lt__(self, other): ... + def __le__(self, other): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __gt__(self, other): ... + def __ge__(self, other): ... + def __add__(self, other): ... + def __radd__(self, other): ... + def __iadd__(self, other): ... + def __sub__(self, other): ... + def __rsub__(self, other): ... + def __isub__(self, other): ... + def __mul__(self, other): ... + def __rmul__(self, other): ... + def __imul__(self, other): ... + if sys.version_info[0] < 3: + def __div__(self, other): ... + def __rdiv__(self, other): ... + def __idiv__(self, other): ... + def __truediv__(self, other): ... + def __rtruediv__(self, other): ... + def __itruediv__(self, other): ... + def __floordiv__(self, other): ... + def __rfloordiv__(self, other): ... + def __ifloordiv__(self, other): ... + def __mod__(self, other): ... + def __rmod__(self, other): ... + def __imod__(self, other): ... + def __divmod__(self, other): ... + def __rdivmod__(self, other): ... + # NumPy's __pow__ doesn't handle a third argument + def __pow__(self, other): ... + def __rpow__(self, other): ... + def __ipow__(self, other): ... + def __lshift__(self, other): ... + def __rlshift__(self, other): ... + def __ilshift__(self, other): ... + def __rshift__(self, other): ... + def __rrshift__(self, other): ... + def __irshift__(self, other): ... + def __and__(self, other): ... + def __rand__(self, other): ... + def __iand__(self, other): ... + def __xor__(self, other): ... + def __rxor__(self, other): ... + def __ixor__(self, other): ... + def __or__(self, other): ... + def __ror__(self, other): ... + def __ior__(self, other): ... + if sys.version_info[:2] >= (3, 5): + def __matmul__(self, other): ... + def __rmatmul__(self, other): ... + def __neg__(self: _ArraySelf) -> _ArraySelf: ... + def __pos__(self: _ArraySelf) -> _ArraySelf: ... + def __abs__(self: _ArraySelf) -> _ArraySelf: ... + def __invert__(self: _ArraySelf) -> _ArraySelf: ... + # TODO(shoyer): remove when all methods are defined + def __getattr__(self, name) -> Any: ... + +_BufferType = Union[ndarray, bytes, bytearray, memoryview] + +class ndarray(_ArrayOrScalarCommon, Iterable, Sized, Container): + @property + def real(self: _ArraySelf) -> _ArraySelf: ... + @real.setter + def real(self, value: ArrayLike) -> None: ... + @property + def imag(self: _ArraySelf) -> _ArraySelf: ... + @imag.setter + def imag(self, value: ArrayLike) -> None: ... + def __new__( + cls: Type[_ArraySelf], + shape: Sequence[int], + dtype: DtypeLike = ..., + buffer: _BufferType = ..., + offset: int = ..., + strides: _ShapeLike = ..., + order: Optional[str] = ..., + ) -> _ArraySelf: ... + @property + def dtype(self) -> _Dtype: ... + @property + def ctypes(self) -> _ctypes: ... + @property + def shape(self) -> _Shape: ... + @shape.setter + def shape(self, value: _ShapeLike): ... + @property + def flat(self: _ArraySelf) -> flatiter[_ArraySelf]: ... + @property + def strides(self) -> _Shape: ... + @strides.setter + def strides(self, value: _ShapeLike): ... + # Array conversion + @overload + def item(self, *args: int) -> Any: ... + @overload + def item(self, args: Tuple[int, ...]) -> Any: ... + def tolist(self) -> List[Any]: ... + @overload + def itemset(self, __value: Any) -> None: ... + @overload + def itemset(self, __item: _ShapeLike, __value: Any) -> None: ... + def tobytes(self, order: Optional[str] = ...) -> bytes: ... + def tofile( + self, fid: Union[IO[bytes], str], sep: str = ..., format: str = ... + ) -> None: ... + def dump(self, file: str) -> None: ... + def dumps(self) -> bytes: ... + def astype( + self: _ArraySelf, + dtype: DtypeLike, + order: str = ..., + casting: str = ..., + subok: bool = ..., + copy: bool = ..., + ) -> _ArraySelf: ... + def byteswap(self: _ArraySelf, inplace: bool = ...) -> _ArraySelf: ... + def copy(self: _ArraySelf, order: str = ...) -> _ArraySelf: ... + @overload + def view(self, type: Type[_NdArraySubClass]) -> _NdArraySubClass: ... + @overload + def view(self: _ArraySelf, dtype: DtypeLike = ...) -> _ArraySelf: ... + @overload + def view( + self, dtype: DtypeLike, type: Type[_NdArraySubClass] + ) -> _NdArraySubClass: ... + def getfield( + self: _ArraySelf, dtype: DtypeLike, offset: int = ... + ) -> _ArraySelf: ... + def setflags( + self, write: bool = ..., align: bool = ..., uic: bool = ... + ) -> None: ... + def fill(self, value: Any) -> None: ... + # Shape manipulation + @overload + def reshape( + self: _ArraySelf, shape: Sequence[int], *, order: str = ... + ) -> _ArraySelf: ... + @overload + def reshape(self: _ArraySelf, *shape: int, order: str = ...) -> _ArraySelf: ... + @overload + def resize(self, new_shape: Sequence[int], *, refcheck: bool = ...) -> None: ... + @overload + def resize(self, *new_shape: int, refcheck: bool = ...) -> None: ... + @overload + def transpose(self: _ArraySelf, axes: Sequence[int]) -> _ArraySelf: ... + @overload + def transpose(self: _ArraySelf, *axes: int) -> _ArraySelf: ... + def swapaxes(self: _ArraySelf, axis1: int, axis2: int) -> _ArraySelf: ... + def flatten(self: _ArraySelf, order: str = ...) -> _ArraySelf: ... + def ravel(self: _ArraySelf, order: str = ...) -> _ArraySelf: ... + def squeeze( + self: _ArraySelf, axis: Union[int, Tuple[int, ...]] = ... + ) -> _ArraySelf: ... + # Many of these special methods are irrelevant currently, since protocols + # aren't supported yet. That said, I'm adding them for completeness. + # https://docs.python.org/3/reference/datamodel.html + def __len__(self) -> int: ... + def __getitem__(self, key) -> Any: ... + def __setitem__(self, key, value): ... + def __iter__(self) -> Any: ... + def __contains__(self, key) -> bool: ... + def __index__(self) -> int: ... + +# NOTE: while `np.generic` is not technically an instance of `ABCMeta`, +# the `@abstractmethod` decorator is herein used to (forcefully) deny +# the creation of `np.generic` instances. +# The `# type: ignore` comments are necessary to silence mypy errors regarding +# the missing `ABCMeta` metaclass. + +# See https://github.com/numpy/numpy-stubs/pull/80 for more details. + +class generic(_ArrayOrScalarCommon): + @abstractmethod + def __init__(self, *args: Any, **kwargs: Any) -> None: ... + @property + def base(self) -> None: ... + +class _real_generic(generic): # type: ignore + @property + def real(self: _ArraySelf) -> _ArraySelf: ... + @property + def imag(self: _ArraySelf) -> _ArraySelf: ... + +class number(generic): ... # type: ignore + +class bool_(_real_generic): + def __init__(self, __value: object = ...) -> None: ... + +class object_(generic): + def __init__(self, __value: object = ...) -> None: ... + +class datetime64: + @overload + def __init__( + self, + __value: Union[None, datetime64, str, dt.datetime] = ..., + __format: str = ... + ) -> None: ... + @overload + def __init__(self, __value: int, __format: str) -> None: ... + def __add__(self, other: Union[timedelta64, int]) -> datetime64: ... + def __sub__(self, other: Union[timedelta64, datetime64, int]) -> timedelta64: ... + +class integer(number, _real_generic): ... # type: ignore +class signedinteger(integer): ... # type: ignore + +class int8(signedinteger): + def __init__(self, __value: SupportsInt = ...) -> None: ... + +class int16(signedinteger): + def __init__(self, __value: SupportsInt = ...) -> None: ... + +class int32(signedinteger): + def __init__(self, __value: SupportsInt = ...) -> None: ... + +class int64(signedinteger): + def __init__(self, __value: SupportsInt = ...) -> None: ... + +class timedelta64(signedinteger): + def __init__(self, __value: Any = ..., __format: str = ...) -> None: ... + @overload + def __add__(self, other: Union[timedelta64, int]) -> timedelta64: ... + @overload + def __add__(self, other: datetime64) -> datetime64: ... + def __sub__(self, other: Union[timedelta64, int]) -> timedelta64: ... + if sys.version_info[0] < 3: + @overload + def __div__(self, other: timedelta64) -> float: ... + @overload + def __div__(self, other: float) -> timedelta64: ... + @overload + def __truediv__(self, other: timedelta64) -> float: ... + @overload + def __truediv__(self, other: float) -> timedelta64: ... + def __mod__(self, other: timedelta64) -> timedelta64: ... + +class unsignedinteger(integer): ... # type: ignore + +class uint8(unsignedinteger): + def __init__(self, __value: SupportsInt = ...) -> None: ... + +class uint16(unsignedinteger): + def __init__(self, __value: SupportsInt = ...) -> None: ... + +class uint32(unsignedinteger): + def __init__(self, __value: SupportsInt = ...) -> None: ... + +class uint64(unsignedinteger): + def __init__(self, __value: SupportsInt = ...) -> None: ... + +class inexact(number): ... # type: ignore +class floating(inexact, _real_generic): ... # type: ignore + +class float16(floating): + def __init__(self, __value: Optional[SupportsFloat] = ...) -> None: ... + +class float32(floating): + def __init__(self, __value: Optional[SupportsFloat] = ...) -> None: ... + +class float64(floating): + def __init__(self, __value: Optional[SupportsFloat] = ...) -> None: ... + +class complexfloating(inexact): ... # type: ignore + +class complex64(complexfloating): + def __init__( + self, + __value: Union[None, SupportsInt, SupportsFloat, SupportsComplex] = ... + ) -> None: ... + @property + def real(self) -> float32: ... + @property + def imag(self) -> float32: ... + +class complex128(complexfloating): + def __init__( + self, + __value: Union[None, SupportsInt, SupportsFloat, SupportsComplex] = ... + ) -> None: ... + @property + def real(self) -> float64: ... + @property + def imag(self) -> float64: ... + +class flexible(_real_generic): ... # type: ignore + +class void(flexible): + def __init__(self, __value: Union[int, integer, bool_, bytes, bytes_]): ... + +class character(_real_generic): ... # type: ignore + +class bytes_(character): + @overload + def __init__(self, __value: object = ...) -> None: ... + @overload + def __init__( + self, __value: Union[str, str_], encoding: str = ..., errors: str = ... + ) -> None: ... + +class str_(character): + @overload + def __init__(self, __value: object = ...) -> None: ... + @overload + def __init__( + self, __value: Union[bytes, bytes_], encoding: str = ..., errors: str = ... + ) -> None: ... + +# TODO(alan): Platform dependent types +# longcomplex, longdouble, longfloat +# bytes, short, intc, intp, longlong +# half, single, double, longdouble +# uint_, int_, float_, complex_ +# float128, complex256 +# float96 + +def array( + object: object, + dtype: DtypeLike = ..., + copy: bool = ..., + subok: bool = ..., + ndmin: int = ..., +) -> ndarray: ... +def zeros( + shape: _ShapeLike, dtype: DtypeLike = ..., order: Optional[str] = ... +) -> ndarray: ... +def ones( + shape: _ShapeLike, dtype: DtypeLike = ..., order: Optional[str] = ... +) -> ndarray: ... +def empty( + shape: _ShapeLike, dtype: DtypeLike = ..., order: Optional[str] = ... +) -> ndarray: ... +def zeros_like( + a: ArrayLike, + dtype: DtypeLike = ..., + order: str = ..., + subok: bool = ..., + shape: Optional[Union[int, Sequence[int]]] = ..., +) -> ndarray: ... +def ones_like( + a: ArrayLike, + dtype: DtypeLike = ..., + order: str = ..., + subok: bool = ..., + shape: Optional[_ShapeLike] = ..., +) -> ndarray: ... +def empty_like( + a: ArrayLike, + dtype: DtypeLike = ..., + order: str = ..., + subok: bool = ..., + shape: Optional[_ShapeLike] = ..., +) -> ndarray: ... +def full( + shape: _ShapeLike, fill_value: Any, dtype: DtypeLike = ..., order: str = ... +) -> ndarray: ... +def full_like( + a: ArrayLike, + fill_value: Any, + dtype: DtypeLike = ..., + order: str = ..., + subok: bool = ..., + shape: Optional[_ShapeLike] = ..., +) -> ndarray: ... +def count_nonzero( + a: ArrayLike, axis: Optional[Union[int, Tuple[int], Tuple[int, int]]] = ... +) -> Union[int, ndarray]: ... +def isfortran(a: ndarray) -> bool: ... +def argwhere(a: ArrayLike) -> ndarray: ... +def flatnonzero(a: ArrayLike) -> ndarray: ... +def correlate(a: ArrayLike, v: ArrayLike, mode: str = ...) -> ndarray: ... +def convolve(a: ArrayLike, v: ArrayLike, mode: str = ...) -> ndarray: ... +def outer(a: ArrayLike, b: ArrayLike, out: ndarray = ...) -> ndarray: ... +def tensordot( + a: ArrayLike, + b: ArrayLike, + axes: Union[ + int, Tuple[int, int], Tuple[Tuple[int, int], ...], Tuple[List[int, int], ...] + ] = ..., +) -> ndarray: ... +def roll( + a: ArrayLike, + shift: Union[int, Tuple[int, ...]], + axis: Optional[Union[int, Tuple[int, ...]]] = ..., +) -> ndarray: ... +def rollaxis(a: ArrayLike, axis: int, start: int = ...) -> ndarray: ... +def moveaxis( + a: ndarray, + source: Union[int, Sequence[int]], + destination: Union[int, Sequence[int]], +) -> ndarray: ... +def cross( + a: ArrayLike, + b: ArrayLike, + axisa: int = ..., + axisb: int = ..., + axisc: int = ..., + axis: Optional[int] = ..., +) -> ndarray: ... +def indices( + dimensions: Sequence[int], dtype: dtype = ..., sparse: bool = ... +) -> Union[ndarray, Tuple[ndarray, ...]]: ... +def fromfunction(function: Callable, shape: Tuple[int, int], **kwargs) -> Any: ... +def isscalar(element: Any) -> bool: ... +def binary_repr(num: int, width: Optional[int] = ...) -> str: ... +def base_repr(number: int, base: int = ..., padding: int = ...) -> str: ... +def identity(n: int, dtype: DtypeLike = ...) -> ndarray: ... +def allclose( + a: ArrayLike, + b: ArrayLike, + rtol: float = ..., + atol: float = ..., + equal_nan: bool = ..., +) -> bool: ... +def isclose( + a: ArrayLike, + b: ArrayLike, + rtol: float = ..., + atol: float = ..., + equal_nan: bool = ..., +) -> Union[bool_, ndarray]: ... +def array_equal(a1: ArrayLike, a2: ArrayLike) -> bool: ... +def array_equiv(a1: ArrayLike, a2: ArrayLike) -> bool: ... + +# +# Constants +# + +Inf: float +Infinity: float +NAN: float +NINF: float +NZERO: float +NaN: float +PINF: float +PZERO: float +e: float +euler_gamma: float +inf: float +infty: float +nan: float +pi: float + +ALLOW_THREADS: int +BUFSIZE: int +CLIP: int +ERR_CALL: int +ERR_DEFAULT: int +ERR_IGNORE: int +ERR_LOG: int +ERR_PRINT: int +ERR_RAISE: int +ERR_WARN: int +FLOATING_POINT_SUPPORT: int +FPE_DIVIDEBYZERO: int +FPE_INVALID: int +FPE_OVERFLOW: int +FPE_UNDERFLOW: int +MAXDIMS: int +MAY_SHARE_BOUNDS: int +MAY_SHARE_EXACT: int +RAISE: int +SHIFT_DIVIDEBYZERO: int +SHIFT_INVALID: int +SHIFT_OVERFLOW: int +SHIFT_UNDERFLOW: int +UFUNC_BUFSIZE_DEFAULT: int +WRAP: int +little_endian: int +tracemalloc_domain: int + +class ufunc: + @property + def __name__(self) -> str: ... + def __call__( + self, + *args: ArrayLike, + out: Optional[Union[ndarray, Tuple[ndarray, ...]]] = ..., + where: Optional[ndarray] = ..., + # The list should be a list of tuples of ints, but since we + # don't know the signature it would need to be + # Tuple[int, ...]. But, since List is invariant something like + # e.g. List[Tuple[int, int]] isn't a subtype of + # List[Tuple[int, ...]], so we can't type precisely here. + axes: List[Any] = ..., + axis: int = ..., + keepdims: bool = ..., + # TODO: make this precise when we can use Literal. + casting: str = ..., + # TODO: make this precise when we can use Literal. + order: Optional[str] = ..., + dtype: DtypeLike = ..., + subok: bool = ..., + signature: Union[str, Tuple[str]] = ..., + # In reality this should be a length of list 3 containing an + # int, an int, and a callable, but there's no way to express + # that. + extobj: List[Union[int, Callable]] = ..., + ) -> Union[ndarray, generic]: ... + @property + def nin(self) -> int: ... + @property + def nout(self) -> int: ... + @property + def nargs(self) -> int: ... + @property + def ntypes(self) -> int: ... + @property + def types(self) -> List[str]: ... + # Broad return type because it has to encompass things like + # + # >>> np.logical_and.identity is True + # True + # >>> np.add.identity is 0 + # True + # >>> np.sin.identity is None + # True + # + # and any user-defined ufuncs. + @property + def identity(self) -> Any: ... + # This is None for ufuncs and a string for gufuncs. + @property + def signature(self) -> Optional[str]: ... + # The next four methods will always exist, but they will just + # raise a ValueError ufuncs with that don't accept two input + # arguments and return one output argument. Because of that we + # can't type them very precisely. + @property + def reduce(self) -> Any: ... + @property + def accumulate(self) -> Any: ... + @property + def reduceat(self) -> Any: ... + @property + def outer(self) -> Any: ... + # Similarly at won't be defined for ufuncs that return multiple + # outputs, so we can't type it very precisely. + @property + def at(self) -> Any: ... + +absolute: ufunc +add: ufunc +arccos: ufunc +arccosh: ufunc +arcsin: ufunc +arcsinh: ufunc +arctan2: ufunc +arctan: ufunc +arctanh: ufunc +bitwise_and: ufunc +bitwise_or: ufunc +bitwise_xor: ufunc +cbrt: ufunc +ceil: ufunc +conjugate: ufunc +copysign: ufunc +cos: ufunc +cosh: ufunc +deg2rad: ufunc +degrees: ufunc +divmod: ufunc +equal: ufunc +exp2: ufunc +exp: ufunc +expm1: ufunc +fabs: ufunc +float_power: ufunc +floor: ufunc +floor_divide: ufunc +fmax: ufunc +fmin: ufunc +fmod: ufunc +frexp: ufunc +gcd: ufunc +greater: ufunc +greater_equal: ufunc +heaviside: ufunc +hypot: ufunc +invert: ufunc +isfinite: ufunc +isinf: ufunc +isnan: ufunc +isnat: ufunc +lcm: ufunc +ldexp: ufunc +left_shift: ufunc +less: ufunc +less_equal: ufunc +log10: ufunc +log1p: ufunc +log2: ufunc +log: ufunc +logaddexp2: ufunc +logaddexp: ufunc +logical_and: ufunc +logical_not: ufunc +logical_or: ufunc +logical_xor: ufunc +matmul: ufunc +maximum: ufunc +minimum: ufunc +modf: ufunc +multiply: ufunc +negative: ufunc +nextafter: ufunc +not_equal: ufunc +positive: ufunc +power: ufunc +rad2deg: ufunc +radians: ufunc +reciprocal: ufunc +remainder: ufunc +right_shift: ufunc +rint: ufunc +sign: ufunc +signbit: ufunc +sin: ufunc +sinh: ufunc +spacing: ufunc +sqrt: ufunc +square: ufunc +subtract: ufunc +tan: ufunc +tanh: ufunc +true_divide: ufunc +trunc: ufunc + +abs = absolute + +# Warnings +class ModuleDeprecationWarning(DeprecationWarning): ... +class VisibleDeprecationWarning(UserWarning): ... +class ComplexWarning(RuntimeWarning): ... +class RankWarning(UserWarning): ... + +# Errors +class TooHardError(RuntimeError): ... + +class AxisError(ValueError, IndexError): + def __init__( + self, axis: int, ndim: Optional[int] = ..., msg_prefix: Optional[str] = ... + ) -> None: ... + +# Functions from np.core.numerictypes +_DefaultType = TypeVar("_DefaultType") + +def maximum_sctype(t: DtypeLike) -> dtype: ... +def issctype(rep: object) -> bool: ... +@overload +def obj2sctype(rep: object) -> Optional[generic]: ... +@overload +def obj2sctype(rep: object, default: None) -> Optional[generic]: ... +@overload +def obj2sctype( + rep: object, default: Type[_DefaultType] +) -> Union[generic, Type[_DefaultType]]: ... +def issubclass_(arg1: object, arg2: Union[object, Tuple[object, ...]]) -> bool: ... +def issubsctype( + arg1: Union[ndarray, DtypeLike], arg2: Union[ndarray, DtypeLike] +) -> bool: ... +def issubdtype(arg1: DtypeLike, arg2: DtypeLike) -> bool: ... +def sctype2char(sctype: object) -> str: ... +def find_common_type( + array_types: Sequence[DtypeLike], scalar_types: Sequence[DtypeLike] +) -> dtype: ... + +# Functions from np.core.fromnumeric +_Mode = Literal["raise", "wrap", "clip"] +_Order = Literal["C", "F", "A"] +_PartitionKind = Literal["introselect"] +_SortKind = Literal["quicksort", "mergesort", "heapsort", "stable"] +_Side = Literal["left", "right"] + +# Various annotations for scalars + +# While dt.datetime and dt.timedelta are not technically part of NumPy, +# they are one of the rare few builtin scalars which serve as valid return types. +# See https://github.com/numpy/numpy-stubs/pull/67#discussion_r412604113. +_ScalarNumpy = Union[generic, dt.datetime, dt.timedelta] +_ScalarBuiltin = Union[str, bytes, dt.date, dt.timedelta, bool, int, float, complex] +_Scalar = Union[_ScalarBuiltin, _ScalarNumpy] + +# Integers and booleans can generally be used interchangeably +_ScalarIntOrBool = TypeVar("_ScalarIntOrBool", bound=Union[integer, bool_]) +_ScalarGeneric = TypeVar("_ScalarGeneric", bound=generic) +_ScalarGenericDT = TypeVar( + "_ScalarGenericDT", bound=Union[dt.datetime, dt.timedelta, generic] +) + +_Number = TypeVar('_Number', bound=number) +_NumberLike = Union[int, float, complex, number, bool_] + +# An array-like object consisting of integers +_Int = Union[int, integer] +_Bool = Union[bool, bool_] +_IntOrBool = Union[_Int, _Bool] +_ArrayLikeIntNested = ArrayLike # TODO: wait for support for recursive types +_ArrayLikeBoolNested = ArrayLike # TODO: wait for support for recursive types + +# Integers and booleans can generally be used interchangeably +_ArrayLikeIntOrBool = Union[ + _IntOrBool, + ndarray, + Sequence[_IntOrBool], + Sequence[_ArrayLikeIntNested], + Sequence[_ArrayLikeBoolNested], +] +_ArrayLikeBool = Union[ + _Bool, + Sequence[_Bool], + ndarray +] + +# The signature of take() follows a common theme with its overloads: +# 1. A generic comes in; the same generic comes out +# 2. A scalar comes in; a generic comes out +# 3. An array-like object comes in; some keyword ensures that a generic comes out +# 4. An array-like object comes in; an ndarray or generic comes out +@overload +def take( + a: _ScalarGenericDT, + indices: int, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> _ScalarGenericDT: ... +@overload +def take( + a: _Scalar, + indices: int, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> _ScalarNumpy: ... +@overload +def take( + a: ArrayLike, + indices: int, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> _ScalarNumpy: ... +@overload +def take( + a: ArrayLike, + indices: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> Union[_ScalarNumpy, ndarray]: ... +def reshape(a: ArrayLike, newshape: _ShapeLike, order: _Order = ...) -> ndarray: ... +@overload +def choose( + a: _ScalarIntOrBool, + choices: ArrayLike, + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> _ScalarIntOrBool: ... +@overload +def choose( + a: _IntOrBool, choices: ArrayLike, out: Optional[ndarray] = ..., mode: _Mode = ... +) -> Union[integer, bool_]: ... +@overload +def choose( + a: _ArrayLikeIntOrBool, + choices: ArrayLike, + out: Optional[ndarray] = ..., + mode: _Mode = ..., +) -> ndarray: ... +def repeat( + a: ArrayLike, repeats: _ArrayLikeIntOrBool, axis: Optional[int] = ... +) -> ndarray: ... +def put( + a: ndarray, ind: _ArrayLikeIntOrBool, v: ArrayLike, mode: _Mode = ... +) -> None: ... +def swapaxes(a: ArrayLike, axis1: int, axis2: int) -> ndarray: ... +def transpose( + a: ArrayLike, axes: Union[None, Sequence[int], ndarray] = ... +) -> ndarray: ... +def partition( + a: ArrayLike, + kth: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + kind: _PartitionKind = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +@overload +def argpartition( + a: generic, + kth: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + kind: _PartitionKind = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> integer: ... +@overload +def argpartition( + a: _ScalarBuiltin, + kth: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + kind: _PartitionKind = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +@overload +def argpartition( + a: ArrayLike, + kth: _ArrayLikeIntOrBool, + axis: Optional[int] = ..., + kind: _PartitionKind = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +def sort( + a: ArrayLike, + axis: Optional[int] = ..., + kind: Optional[_SortKind] = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +def argsort( + a: ArrayLike, + axis: Optional[int] = ..., + kind: Optional[_SortKind] = ..., + order: Union[None, str, Sequence[str]] = ..., +) -> ndarray: ... +@overload +def argmax(a: ArrayLike, axis: None = ..., out: Optional[ndarray] = ...) -> integer: ... +@overload +def argmax( + a: ArrayLike, axis: int = ..., out: Optional[ndarray] = ... +) -> Union[integer, ndarray]: ... +@overload +def argmin(a: ArrayLike, axis: None = ..., out: Optional[ndarray] = ...) -> integer: ... +@overload +def argmin( + a: ArrayLike, axis: int = ..., out: Optional[ndarray] = ... +) -> Union[integer, ndarray]: ... +@overload +def searchsorted( + a: ArrayLike, + v: _Scalar, + side: _Side = ..., + sorter: Optional[_ArrayLikeIntOrBool] = ..., # 1D int array +) -> integer: ... +@overload +def searchsorted( + a: ArrayLike, + v: ArrayLike, + side: _Side = ..., + sorter: Optional[_ArrayLikeIntOrBool] = ..., # 1D int array +) -> ndarray: ... +def resize(a: ArrayLike, new_shape: _ShapeLike) -> ndarray: ... +@overload +def squeeze(a: _ScalarGeneric, axis: Optional[_ShapeLike] = ...) -> _ScalarGeneric: ... +@overload +def squeeze(a: ArrayLike, axis: Optional[_ShapeLike] = ...) -> ndarray: ... +def diagonal( + a: ArrayLike, offset: int = ..., axis1: int = ..., axis2: int = ... # >= 2D array +) -> ndarray: ... +def trace( + a: ArrayLike, # >= 2D array + offset: int = ..., + axis1: int = ..., + axis2: int = ..., + dtype: DtypeLike = ..., + out: Optional[ndarray] = ..., +) -> Union[number, ndarray]: ... +def ravel(a: ArrayLike, order: _Order = ...) -> ndarray: ... +def nonzero(a: ArrayLike) -> Tuple[ndarray, ...]: ... +def shape(a: ArrayLike) -> _Shape: ... +def compress( + condition: ArrayLike, # 1D bool array + a: ArrayLike, + axis: Optional[int] = ..., + out: Optional[ndarray] = ..., +) -> ndarray: ... +@overload +def clip( + a: _Number, + a_min: ArrayLike, + a_max: Optional[ArrayLike], + out: Optional[ndarray] = ..., + **kwargs: Any, +) -> _Number: ... +@overload +def clip( + a: _Number, + a_min: None, + a_max: ArrayLike, + out: Optional[ndarray] = ..., + **kwargs: Any, +) -> _Number: ... +@overload +def clip( + a: ArrayLike, + a_min: ArrayLike, + a_max: Optional[ArrayLike], + out: Optional[ndarray] = ..., + **kwargs: Any, +) -> Union[number, ndarray]: ... +@overload +def clip( + a: ArrayLike, + a_min: None, + a_max: ArrayLike, + out: Optional[ndarray] = ..., + **kwargs: Any, +) -> Union[number, ndarray]: ... +@overload +def sum( + a: _Number, + axis: Optional[_ShapeLike] = ..., + dtype: DtypeLike = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., + initial: _NumberLike = ..., + where: _ArrayLikeBool = ..., +) -> _Number: ... +@overload +def sum( + a: ArrayLike, + axis: _ShapeLike = ..., + dtype: DtypeLike = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., + initial: _NumberLike = ..., + where: _ArrayLikeBool = ..., +) -> Union[number, ndarray]: ... +@overload +def all( + a: ArrayLike, + axis: None = ..., + out: Optional[ndarray] = ..., + keepdims: Literal[False] = ..., +) -> bool_: ... +@overload +def all( + a: ArrayLike, + axis: Optional[_ShapeLike] = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., +) -> Union[bool_, ndarray]: ... +@overload +def any( + a: ArrayLike, + axis: None = ..., + out: Optional[ndarray] = ..., + keepdims: Literal[False] = ..., +) -> bool_: ... +@overload +def any( + a: ArrayLike, + axis: Optional[_ShapeLike] = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., +) -> Union[bool_, ndarray]: ... +def cumsum( + a: ArrayLike, + axis: Optional[int] = ..., + dtype: DtypeLike = ..., + out: Optional[ndarray] = ..., +) -> ndarray: ... +@overload +def ptp( + a: _Number, + axis: Optional[_ShapeLike] = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., +) -> _Number: ... +@overload +def ptp( + a: ArrayLike, + axis: None = ..., + out: Optional[ndarray] = ..., + keepdims: Literal[False] = ..., +) -> number: ... +@overload +def ptp( + a: ArrayLike, + axis: Optional[_ShapeLike] = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., +) -> Union[number, ndarray]: ... +@overload +def amax( + a: _Number, + axis: Optional[_ShapeLike] = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., + initial: _NumberLike = ..., + where: _ArrayLikeBool = ..., +) -> _Number: ... +@overload +def amax( + a: ArrayLike, + axis: None = ..., + out: Optional[ndarray] = ..., + keepdims: Literal[False] = ..., + initial: _NumberLike = ..., + where: _ArrayLikeBool = ..., +) -> number: ... +@overload +def amax( + a: ArrayLike, + axis: Optional[_ShapeLike] = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., + initial: _NumberLike = ..., + where: _ArrayLikeBool = ..., +) -> Union[number, ndarray]: ... +@overload +def amin( + a: _Number, + axis: Optional[_ShapeLike] = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., + initial: _NumberLike = ..., + where: _ArrayLikeBool = ..., +) -> _Number: ... +@overload +def amin( + a: ArrayLike, + axis: None = ..., + out: Optional[ndarray] = ..., + keepdims: Literal[False] = ..., + initial: _NumberLike = ..., + where: _ArrayLikeBool = ..., +) -> number: ... +@overload +def amin( + a: ArrayLike, + axis: Optional[_ShapeLike] = ..., + out: Optional[ndarray] = ..., + keepdims: bool = ..., + initial: _NumberLike = ..., + where: _ArrayLikeBool = ..., +) -> Union[number, ndarray]: ... diff --git a/numpy/_pytesttester.py b/numpy/_pytesttester.py index ca86aeb22..1c32367f3 100644 --- a/numpy/_pytesttester.py +++ b/numpy/_pytesttester.py @@ -35,12 +35,27 @@ __all__ = ['PytestTester'] def _show_numpy_info(): + from numpy.core._multiarray_umath import ( + __cpu_features__, __cpu_baseline__, __cpu_dispatch__ + ) import numpy as np print("NumPy version %s" % np.__version__) relaxed_strides = np.ones((10, 1), order="C").flags.f_contiguous print("NumPy relaxed strides checking option:", relaxed_strides) + if len(__cpu_baseline__) == 0 and len(__cpu_dispatch__) == 0: + enabled_features = "nothing enabled" + else: + enabled_features = ' '.join(__cpu_baseline__) + for feature in __cpu_dispatch__: + if __cpu_features__[feature]: + enabled_features += " %s*" % feature + else: + enabled_features += " %s?" % feature + print("NumPy CPU features:", enabled_features) + + class PytestTester: """ diff --git a/numpy/core/_add_newdocs.py b/numpy/core/_add_newdocs.py index 20ad39b05..2461971eb 100644 --- a/numpy/core/_add_newdocs.py +++ b/numpy/core/_add_newdocs.py @@ -1307,14 +1307,14 @@ add_newdoc('numpy.core.multiarray', 'arange', Parameters ---------- - start : number, optional + start : integer or real, optional Start of interval. The interval includes this value. The default start value is 0. - stop : number + stop : integer or real End of interval. The interval does not include this value, except in some cases where `step` is not an integer and floating point round-off affects the length of `out`. - step : number, optional + step : integer or real, optional Spacing between values. For any output `out`, this is the distance between two adjacent values, ``out[i+1] - out[i]``. The default step size is 1. If `step` is specified as a position argument, @@ -1525,7 +1525,7 @@ add_newdoc('numpy.core.multiarray', 'c_einsum', Controls the memory layout of the output. 'C' means it should be C contiguous. 'F' means it should be Fortran contiguous, 'A' means it should be 'F' if the inputs are all 'F', 'C' otherwise. - 'K' means it should be as close to the layout as the inputs as + 'K' means it should be as close to the layout of the inputs as is possible, including arbitrarily permuted axes. Default is 'K'. casting : {'no', 'equiv', 'safe', 'same_kind', 'unsafe'}, optional @@ -3243,15 +3243,13 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('newbyteorder', below. `new_order` codes can be any of: * 'S' - swap dtype from current to opposite endian - * {'<', 'L'} - little endian - * {'>', 'B'} - big endian - * {'=', 'N'} - native order + * {'<', 'little'} - little endian + * {'>', 'big'} - big endian + * '=' - native order, equivalent to `sys.byteorder` * {'|', 'I'} - ignore (no change to byte order) The default value ('S') results in swapping the current - byte order. The code does a case-insensitive check on the first - letter of `new_order` for the alternatives above. For example, - any of 'B' or 'b' or 'biggish' are valid to specify big-endian. + byte order. Returns @@ -3753,7 +3751,7 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('squeeze', """ a.squeeze(axis=None) - Remove single-dimensional entries from the shape of `a`. + Remove axes of length one from `a`. Refer to `numpy.squeeze` for full documentation. @@ -3938,18 +3936,17 @@ add_newdoc('numpy.core.multiarray', 'ndarray', ('tobytes', """ Construct Python bytes containing the raw data bytes in the array. Constructs Python bytes showing a copy of the raw contents of - data memory. The bytes object can be produced in either 'C' or 'Fortran', - or 'Any' order (the default is 'C'-order). 'Any' order means C-order - unless the F_CONTIGUOUS flag in the array is set, in which case it - means 'Fortran' order. + data memory. The bytes object is produced in C-order by default. + This behavior is controlled by the ``order`` parameter. .. versionadded:: 1.9.0 Parameters ---------- - order : {'C', 'F', None}, optional - Order of the data for multidimensional arrays: - C, Fortran, or the same as for the original array. + order : {'C', 'F', 'A'}, optional + Controls the memory layout of the bytes object. 'C' means C-order, + 'F' means F-order, 'A' (short for *Any*) means 'F' if `a` is + Fortran contiguous, 'C' otherwise. Default is 'C'. Returns ------- @@ -5144,7 +5141,7 @@ add_newdoc('numpy.core', 'ufunc', ('at', add_newdoc('numpy.core.multiarray', 'dtype', """ - dtype(obj, align=False, copy=False) + dtype(dtype, align=False, copy=False) Create a data type object. @@ -5154,7 +5151,7 @@ add_newdoc('numpy.core.multiarray', 'dtype', Parameters ---------- - obj + dtype Object to be converted to a data type object. align : bool, optional Add padding to the fields to match what a C compiler would output @@ -5666,15 +5663,11 @@ add_newdoc('numpy.core.multiarray', 'dtype', ('newbyteorder', byte order. `new_order` codes can be any of: * 'S' - swap dtype from current to opposite endian - * {'<', 'L'} - little endian - * {'>', 'B'} - big endian - * {'=', 'N'} - native order + * {'<', 'little'} - little endian + * {'>', 'big'} - big endian + * '=' - native order * {'|', 'I'} - ignore (no change to byte order) - The code does a case-insensitive check on the first letter of - `new_order` for these alternatives. For example, any of '>' - or 'B' or 'b' or 'brian' are valid to specify big-endian. - Returns ------- new_dtype : dtype @@ -6041,9 +6034,9 @@ add_newdoc('numpy.core.numerictypes', 'generic', ('newbyteorder', The `new_order` code can be any from the following: * 'S' - swap dtype from current to opposite endian - * {'<', 'L'} - little endian - * {'>', 'B'} - big endian - * {'=', 'N'} - native order + * {'<', 'little'} - little endian + * {'>', 'big'} - big endian + * '=' - native order * {'|', 'I'} - ignore (no change to byte order) Parameters @@ -6051,9 +6044,7 @@ add_newdoc('numpy.core.numerictypes', 'generic', ('newbyteorder', new_order : str, optional Byte order to force; a value from the byte order specifications above. The default value ('S') results in swapping the current - byte order. The code does a case-insensitive check on the first - letter of `new_order` for the alternatives above. For example, - any of 'B' or 'b' or 'biggish' are valid to specify big-endian. + byte order. Returns diff --git a/numpy/core/_asarray.py b/numpy/core/_asarray.py index df569f22d..1b06c328f 100644 --- a/numpy/core/_asarray.py +++ b/numpy/core/_asarray.py @@ -23,9 +23,12 @@ def asarray(a, dtype=None, order=None): of lists and ndarrays. dtype : data-type, optional By default, the data-type is inferred from the input data. - order : {'C', 'F'}, optional - Whether to use row-major (C-style) or - column-major (Fortran-style) memory representation. + order : {'C', 'F', 'A', 'K'}, optional + Memory layout. 'A' and 'K' depend on the order of input array a. + 'C' row-major (C-style), + 'F' column-major (Fortran-style) memory representation. + 'A' (any) means 'F' if `a` is Fortran contiguous, 'C' otherwise + 'K' (keep) preserve input order Defaults to 'C'. Returns @@ -95,9 +98,13 @@ def asanyarray(a, dtype=None, order=None): tuples of lists, and ndarrays. dtype : data-type, optional By default, the data-type is inferred from the input data. - order : {'C', 'F'}, optional - Whether to use row-major (C-style) or column-major - (Fortran-style) memory representation. Defaults to 'C'. + order : {'C', 'F', 'A', 'K'}, optional + Memory layout. 'A' and 'K' depend on the order of input array a. + 'C' row-major (C-style), + 'F' column-major (Fortran-style) memory representation. + 'A' (any) means 'F' if `a` is Fortran contiguous, 'C' otherwise + 'K' (keep) preserve input order + Defaults to 'C'. Returns ------- diff --git a/numpy/core/_dtype.py b/numpy/core/_dtype.py index 76d0b8149..50aeeb5bc 100644 --- a/numpy/core/_dtype.py +++ b/numpy/core/_dtype.py @@ -160,13 +160,13 @@ def _scalar_str(dtype, short): def _byte_order_str(dtype): """ Normalize byteorder to '<' or '>' """ # hack to obtain the native and swapped byte order characters - swapped = np.dtype(int).newbyteorder('s') - native = swapped.newbyteorder('s') + swapped = np.dtype(int).newbyteorder('S') + native = swapped.newbyteorder('S') byteorder = dtype.byteorder if byteorder == '=': return native.byteorder - if byteorder == 's': + if byteorder == 'S': # TODO: this path can never be reached return swapped.byteorder elif byteorder == '|': diff --git a/numpy/core/_internal.py b/numpy/core/_internal.py index 1378497bb..85853622a 100644 --- a/numpy/core/_internal.py +++ b/numpy/core/_internal.py @@ -162,8 +162,9 @@ def _commastring(astr): try: (order1, repeats, order2, dtype) = mo.groups() except (TypeError, AttributeError): - raise ValueError('format number %d of "%s" is not recognized' % - (len(result)+1, astr)) + raise ValueError( + f'format number {len(result)+1} of "{astr}" is not recognized' + ) from None startindex = mo.end() # Separator or ending padding if startindex < len(astr): @@ -373,9 +374,9 @@ def _newnames(datatype, order): nameslist.remove(name) except ValueError: if name in seen: - raise ValueError("duplicate field name: %s" % (name,)) + raise ValueError(f"duplicate field name: {name}") from None else: - raise ValueError("unknown field name: %s" % (name,)) + raise ValueError(f"unknown field name: {name}") from None seen.add(name) return tuple(list(order) + nameslist) raise ValueError("unsupported order value: %s" % (order,)) diff --git a/numpy/core/_internal.pyi b/numpy/core/_internal.pyi new file mode 100644 index 000000000..1b3889e51 --- /dev/null +++ b/numpy/core/_internal.pyi @@ -0,0 +1,18 @@ +from typing import Any + +# TODO: add better annotations when ctypes is stubbed out + +class _ctypes: + @property + def data(self) -> int: ... + @property + def shape(self) -> Any: ... + @property + def strides(self) -> Any: ... + def data_as(self, obj: Any) -> Any: ... + def shape_as(self, obj: Any) -> Any: ... + def strides_as(self, obj: Any) -> Any: ... + def get_data(self) -> int: ... + def get_shape(self) -> Any: ... + def get_strides(self) -> Any: ... + def get_as_parameter(self) -> Any: ... diff --git a/numpy/core/_type_aliases.py b/numpy/core/_type_aliases.py index c26431443..de90fd818 100644 --- a/numpy/core/_type_aliases.py +++ b/numpy/core/_type_aliases.py @@ -11,40 +11,19 @@ and sometimes other mappings too. .. data:: sctypeDict Similar to `allTypes`, but maps a broader set of aliases to their types. -.. data:: sctypeNA - NumArray-compatible names for the scalar types. Contains not only - ``name: type`` mappings, but ``char: name`` mappings too. - - .. deprecated:: 1.16 - .. data:: sctypes A dictionary keyed by a "type group" string, providing a list of types under that group. """ -import warnings from numpy.compat import unicode -from numpy._globals import VisibleDeprecationWarning -from numpy.core._string_helpers import english_lower, english_capitalize +from numpy.core._string_helpers import english_lower from numpy.core.multiarray import typeinfo, dtype from numpy.core._dtype import _kind_name sctypeDict = {} # Contains all leaf-node scalar types with aliases -class TypeNADict(dict): - def __getitem__(self, key): - # 2018-06-24, 1.16 - warnings.warn('sctypeNA and typeNA will be removed in v1.18 ' - 'of numpy', VisibleDeprecationWarning, stacklevel=2) - return dict.__getitem__(self, key) - def get(self, key, default=None): - # 2018-06-24, 1.16 - warnings.warn('sctypeNA and typeNA will be removed in v1.18 ' - 'of numpy', VisibleDeprecationWarning, stacklevel=2) - return dict.get(self, key, default) - -sctypeNA = TypeNADict() # Contails all leaf-node types -> numarray type equivalences allTypes = {} # Collect the types we will add to the module @@ -127,27 +106,24 @@ def _add_aliases(): if name in ('longdouble', 'clongdouble') and myname in allTypes: continue - base_capitalize = english_capitalize(base) - if base == 'complex': - na_name = '%s%d' % (base_capitalize, bit//2) - elif base == 'bool': - na_name = base_capitalize - else: - na_name = "%s%d" % (base_capitalize, bit) - allTypes[myname] = info.type # add mapping for both the bit name and the numarray name sctypeDict[myname] = info.type - sctypeDict[na_name] = info.type # add forward, reverse, and string mapping to numarray - sctypeNA[na_name] = info.type - sctypeNA[info.type] = na_name - sctypeNA[info.char] = na_name - sctypeDict[char] = info.type - sctypeNA[char] = na_name + + # Add deprecated numeric-style type aliases manually, at some point + # we may want to deprecate the lower case "bytes0" version as well. + for name in ["Bytes0", "Datetime64", "Str0", "Uint32", "Uint64"]: + if english_lower(name) not in allTypes: + # Only one of Uint32 or Uint64, aliases of `np.uintp`, was (and is) defined, note that this + # is not UInt32/UInt64 (capital i), which is removed. + continue + allTypes[name] = allTypes[english_lower(name)] + sctypeDict[name] = sctypeDict[english_lower(name)] + _add_aliases() def _add_integer_aliases(): @@ -157,20 +133,15 @@ def _add_integer_aliases(): u_info = _concrete_typeinfo[u_ctype] bits = i_info.bits # same for both - for info, charname, intname, Intname in [ - (i_info,'i%d' % (bits//8,), 'int%d' % bits, 'Int%d' % bits), - (u_info,'u%d' % (bits//8,), 'uint%d' % bits, 'UInt%d' % bits)]: + for info, charname, intname in [ + (i_info,'i%d' % (bits//8,), 'int%d' % bits), + (u_info,'u%d' % (bits//8,), 'uint%d' % bits)]: if bits not in seen_bits: # sometimes two different types have the same number of bits # if so, the one iterated over first takes precedence allTypes[intname] = info.type sctypeDict[intname] = info.type - sctypeDict[Intname] = info.type sctypeDict[charname] = info.type - sctypeNA[Intname] = info.type - sctypeNA[charname] = info.type - sctypeNA[info.type] = Intname - sctypeNA[info.char] = Intname seen_bits.add(bits) diff --git a/numpy/core/code_generators/genapi.py b/numpy/core/code_generators/genapi.py index d88772bdc..856db0410 100644 --- a/numpy/core/code_generators/genapi.py +++ b/numpy/core/code_generators/genapi.py @@ -21,9 +21,11 @@ __docformat__ = 'restructuredtext' # The files under src/ that are scanned for API functions API_FILES = [join('multiarray', 'alloc.c'), + join('multiarray', 'abstractdtypes.c'), join('multiarray', 'arrayfunction_override.c'), join('multiarray', 'array_assign_array.c'), join('multiarray', 'array_assign_scalar.c'), + join('multiarray', 'array_coercion.c'), join('multiarray', 'arrayobject.c'), join('multiarray', 'arraytypes.c.src'), join('multiarray', 'buffer.c'), diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index 2ce2fdb55..4c8884dc5 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -726,6 +726,7 @@ defdict = { None, TD('e', f='log', astype={'e':'f'}), TD('f', simd=[('fma', 'f'), ('avx512f', 'f')]), + TD('d', simd=[('avx512f', 'd')]), TD('fdg' + cmplx, f='log'), TD(P, f='log'), ), diff --git a/numpy/core/einsumfunc.py b/numpy/core/einsumfunc.py index c46ae173d..f65f4015c 100644 --- a/numpy/core/einsumfunc.py +++ b/numpy/core/einsumfunc.py @@ -1358,11 +1358,18 @@ def einsum(*operands, out=None, optimize=False, **kwargs): raise TypeError("Did not understand the following kwargs: %s" % unknown_kwargs) - # Build the contraction list and operand operands, contraction_list = einsum_path(*operands, optimize=optimize, einsum_call=True) + # Handle order kwarg for output array, c_einsum allows mixed case + output_order = kwargs.pop('order', 'K') + if output_order.upper() == 'A': + if all(arr.flags.f_contiguous for arr in operands): + output_order = 'F' + else: + output_order = 'C' + # Start contraction loop for num, contraction in enumerate(contraction_list): inds, idx_rm, einsum_str, remaining, blas = contraction @@ -1412,4 +1419,4 @@ def einsum(*operands, out=None, optimize=False, **kwargs): if specified_out: return out else: - return operands[0] + return asanyarray(operands[0], order=output_order) diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py index 2b88ccedf..f8c11c015 100644 --- a/numpy/core/fromnumeric.py +++ b/numpy/core/fromnumeric.py @@ -459,7 +459,8 @@ def repeat(a, repeats, axis=None): See Also -------- tile : Tile an array. - + unique : Find the unique elements of an array. + Examples -------- >>> np.repeat(3, 4) @@ -649,6 +650,10 @@ def transpose(a, axes=None): >>> np.transpose(x, (1, 0, 2)).shape (2, 1, 3) + >>> x = np.ones((2, 3, 4, 5)) + >>> np.transpose(x).shape + (5, 4, 3, 2) + """ return _wrapfunc(a, 'transpose', axes) @@ -1434,7 +1439,7 @@ def _squeeze_dispatcher(a, axis=None): @array_function_dispatch(_squeeze_dispatcher) def squeeze(a, axis=None): """ - Remove single-dimensional entries from the shape of an array. + Remove axes of length one from `a`. Parameters ---------- @@ -1443,7 +1448,7 @@ def squeeze(a, axis=None): axis : None or int or tuple of ints, optional .. versionadded:: 1.7.0 - Selects a subset of the single-dimensional entries in the + Selects a subset of the entries of length one in the shape. If an axis is selected with shape entry greater than one, an error is raised. @@ -1462,7 +1467,7 @@ def squeeze(a, axis=None): See Also -------- - expand_dims : The inverse operation, adding singleton dimensions + expand_dims : The inverse operation, adding entries of length one reshape : Insert, remove, and combine dimensions, and resize existing ones Examples @@ -2846,6 +2851,9 @@ def alen(a): """ Return the length of the first dimension of the input array. + .. deprecated:: 1.18 + `numpy.alen` is deprecated, use `len` instead. + Parameters ---------- a : array_like diff --git a/numpy/core/function_base.py b/numpy/core/function_base.py index 9e46f0ea5..f57e95742 100644 --- a/numpy/core/function_base.py +++ b/numpy/core/function_base.py @@ -52,8 +52,10 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, If True, return (`samples`, `step`), where `step` is the spacing between samples. dtype : dtype, optional - The type of the output array. If `dtype` is not given, infer the data - type from the other input arguments. + The type of the output array. If `dtype` is not given, the data type + is inferred from `start` and `stop`. The inferred dtype will never be + an integer; `float` is chosen even if the arguments would produce an + array of integers. .. versionadded:: 1.9.0 @@ -202,8 +204,10 @@ def logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, ``ln(samples) / ln(base)`` (or ``log_base(samples)``) is uniform. Default is 10.0. dtype : dtype - The type of the output array. If `dtype` is not given, infer the data - type from the other input arguments. + The type of the output array. If `dtype` is not given, the data type + is inferred from `start` and `stop`. The inferred type will never be + an integer; `float` is chosen even if the arguments would produce an + array of integers. axis : int, optional The axis in the result to store the samples. Relevant only if start or stop are array-like. By default (0), the samples will be along a @@ -297,8 +301,10 @@ def geomspace(start, stop, num=50, endpoint=True, dtype=None, axis=0): If true, `stop` is the last sample. Otherwise, it is not included. Default is True. dtype : dtype - The type of the output array. If `dtype` is not given, infer the data - type from the other input arguments. + The type of the output array. If `dtype` is not given, the data type + is inferred from `start` and `stop`. The inferred dtype will never be + an integer; `float` is chosen even if the arguments would produce an + array of integers. axis : int, optional The axis in the result to store the samples. Relevant only if start or stop are array-like. By default (0), the samples will be along a @@ -408,8 +414,18 @@ def geomspace(start, stop, num=50, endpoint=True, dtype=None, axis=0): log_start = _nx.log10(start) log_stop = _nx.log10(stop) - result = out_sign * logspace(log_start, log_stop, num=num, - endpoint=endpoint, base=10.0, dtype=dtype) + result = logspace(log_start, log_stop, num=num, + endpoint=endpoint, base=10.0, dtype=dtype) + + # Make sure the endpoints match the start and stop arguments. This is + # necessary because np.exp(np.log(x)) is not necessarily equal to x. + if num > 0: + result[0] = start + if num > 1 and endpoint: + result[-1] = stop + + result = out_sign * result + if axis != 0: result = _nx.moveaxis(result, 0, axis) diff --git a/numpy/core/getlimits.py b/numpy/core/getlimits.py index f73c21f67..fcb73e8ba 100644 --- a/numpy/core/getlimits.py +++ b/numpy/core/getlimits.py @@ -262,11 +262,15 @@ def _get_machar(ftype): raise ValueError(repr(ftype)) # Detect known / suspected types key = ftype('-0.1').newbyteorder('<').tobytes() - ma_like = _KNOWN_TYPES.get(key) - # Could be 80 bit == 10 byte extended precision, where last bytes can be - # random garbage. Try comparing first 10 bytes to pattern. - if ma_like is None and ftype == ntypes.longdouble: + ma_like = None + if ftype == ntypes.longdouble: + # Could be 80 bit == 10 byte extended precision, where last bytes can + # be random garbage. + # Comparing first 10 bytes to pattern first to avoid branching on the + # random garbage. ma_like = _KNOWN_TYPES.get(key[:10]) + if ma_like is None: + ma_like = _KNOWN_TYPES.get(key) if ma_like is not None: return ma_like # Fall back to parameter discovery diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 1b61899fa..bbcf468c1 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -341,9 +341,6 @@ struct NpyAuxData_tag { #define NPY_ERR(str) fprintf(stderr, #str); fflush(stderr); #define NPY_ERR2(str) fprintf(stderr, str); fflush(stderr); -#define NPY_STRINGIFY(x) #x -#define NPY_TOSTRING(x) NPY_STRINGIFY(x) - /* * Macros to define how array, and dimension/strides data is * allocated. @@ -1550,11 +1547,15 @@ PyArray_GETITEM(const PyArrayObject *arr, const char *itemptr) (void *)itemptr, (PyArrayObject *)arr); } +/* + * SETITEM should only be used if it is known that the value is a scalar + * and of a type understood by the arrays dtype. + * Use `PyArray_Pack` if the value may be of a different dtype. + */ static NPY_INLINE int PyArray_SETITEM(PyArrayObject *arr, char *itemptr, PyObject *v) { - return ((PyArrayObject_fields *)arr)->descr->f->setitem( - v, itemptr, arr); + return ((PyArrayObject_fields *)arr)->descr->f->setitem(v, itemptr, arr); } #else @@ -1823,10 +1824,25 @@ typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size, /* TODO: Make this definition public in the API, as soon as its settled */ NPY_NO_EXPORT extern PyTypeObject PyArrayDTypeMeta_Type; + typedef struct PyArray_DTypeMeta_tag PyArray_DTypeMeta; + + typedef PyArray_Descr *(discover_descr_from_pyobject_function)( + PyArray_DTypeMeta *cls, PyObject *obj); + + /* + * Before making this public, we should decide whether it should pass + * the type, or allow looking at the object. A possible use-case: + * `np.array(np.array([0]), dtype=np.ndarray)` + * Could consider arrays that are not `dtype=ndarray` "scalars". + */ + typedef int (is_known_scalar_type_function)( + PyArray_DTypeMeta *cls, PyTypeObject *obj); + + typedef PyArray_Descr *(default_descr_function)(PyArray_DTypeMeta *cls); + /* * While NumPy DTypes would not need to be heap types the plan is to - * make DTypes available in Python at which point we will probably want - * them to be. + * make DTypes available in Python at which point they will be heap types. * Since we also wish to add fields to the DType class, this looks like * a typical instance definition, but with PyHeapTypeObject instead of * only the PyObject_HEAD. @@ -1834,7 +1850,7 @@ typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size, * it is a fairly complex construct which may be better to allow * refactoring of. */ - typedef struct _PyArray_DTypeMeta { + struct PyArray_DTypeMeta_tag { PyHeapTypeObject super; /* @@ -1873,9 +1889,12 @@ typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size, * NOTE: We could make a copy to detect changes to `f`. */ PyArray_ArrFuncs *f; - } PyArray_DTypeMeta; - #define NPY_DTYPE(descr) ((PyArray_DTypeMeta *)Py_TYPE(descr)) + /* DType methods, these could be moved into its own struct */ + discover_descr_from_pyobject_function *discover_descr_from_pyobject; + is_known_scalar_type_function *is_known_scalar_type; + default_descr_function *default_descr; + }; #endif /* NPY_INTERNAL_BUILD */ diff --git a/numpy/core/include/numpy/npy_common.h b/numpy/core/include/numpy/npy_common.h index 3cec0c6ff..5706e0576 100644 --- a/numpy/core/include/numpy/npy_common.h +++ b/numpy/core/include/numpy/npy_common.h @@ -141,6 +141,14 @@ #define NPY_INLINE #endif +#ifdef _MSC_VER + #define NPY_FINLINE static __forceinline +#elif defined(__GNUC__) + #define NPY_FINLINE static NPY_INLINE __attribute__((always_inline)) +#else + #define NPY_FINLINE static +#endif + #ifdef HAVE___THREAD #define NPY_TLS __thread #else diff --git a/numpy/core/include/numpy/npy_cpu.h b/numpy/core/include/numpy/npy_cpu.h index 5edd8f42e..509e23a51 100644 --- a/numpy/core/include/numpy/npy_cpu.h +++ b/numpy/core/include/numpy/npy_cpu.h @@ -18,6 +18,7 @@ * NPY_CPU_ARCEL * NPY_CPU_ARCEB * NPY_CPU_RISCV64 + * NPY_CPU_WASM */ #ifndef _NPY_CPUARCH_H_ #define _NPY_CPUARCH_H_ @@ -102,6 +103,9 @@ #define NPY_CPU_ARCEB #elif defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 64 #define NPY_CPU_RISCV64 +#elif defined(__EMSCRIPTEN__) + /* __EMSCRIPTEN__ is defined by emscripten: an LLVM-to-Web compiler */ + #define NPY_CPU_WASM #else #error Unknown CPU, please report this to numpy maintainers with \ information about your platform (OS, CPU and compiler) diff --git a/numpy/core/include/numpy/npy_endian.h b/numpy/core/include/numpy/npy_endian.h index 44cdffd14..aa367a002 100644 --- a/numpy/core/include/numpy/npy_endian.h +++ b/numpy/core/include/numpy/npy_endian.h @@ -48,7 +48,8 @@ || defined(NPY_CPU_MIPSEL) \ || defined(NPY_CPU_PPC64LE) \ || defined(NPY_CPU_ARCEL) \ - || defined(NPY_CPU_RISCV64) + || defined(NPY_CPU_RISCV64) \ + || defined(NPY_CPU_WASM) #define NPY_BYTE_ORDER NPY_LITTLE_ENDIAN #elif defined(NPY_CPU_PPC) \ || defined(NPY_CPU_SPARC) \ diff --git a/numpy/core/include/numpy/utils.h b/numpy/core/include/numpy/utils.h index 32218b8c7..e251a5201 100644 --- a/numpy/core/include/numpy/utils.h +++ b/numpy/core/include/numpy/utils.h @@ -2,20 +2,36 @@ #define __NUMPY_UTILS_HEADER__ #ifndef __COMP_NPY_UNUSED - #if defined(__GNUC__) - #define __COMP_NPY_UNUSED __attribute__ ((__unused__)) - # elif defined(__ICC) - #define __COMP_NPY_UNUSED __attribute__ ((__unused__)) - # elif defined(__clang__) - #define __COMP_NPY_UNUSED __attribute__ ((unused)) - #else - #define __COMP_NPY_UNUSED - #endif + #if defined(__GNUC__) + #define __COMP_NPY_UNUSED __attribute__ ((__unused__)) + #elif defined(__ICC) + #define __COMP_NPY_UNUSED __attribute__ ((__unused__)) + #elif defined(__clang__) + #define __COMP_NPY_UNUSED __attribute__ ((unused)) + #else + #define __COMP_NPY_UNUSED + #endif +#endif + +#if defined(__GNUC__) || defined(__ICC) || defined(__clang__) + #define NPY_DECL_ALIGNED(x) __attribute__ ((aligned (x))) +#elif defined(_MSC_VER) + #define NPY_DECL_ALIGNED(x) __declspec(align(x)) +#else + #define NPY_DECL_ALIGNED(x) #endif /* Use this to tag a variable as not used. It will remove unused variable * warning on support platforms (see __COM_NPY_UNUSED) and mangle the variable * to avoid accidental use */ #define NPY_UNUSED(x) (__NPY_UNUSED_TAGGED ## x) __COMP_NPY_UNUSED +#define NPY_EXPAND(x) x + +#define NPY_STRINGIFY(x) #x +#define NPY_TOSTRING(x) NPY_STRINGIFY(x) + +#define NPY_CAT__(a, b) a ## b +#define NPY_CAT_(a, b) NPY_CAT__(a, b) +#define NPY_CAT(a, b) NPY_CAT_(a, b) #endif diff --git a/numpy/core/multiarray.py b/numpy/core/multiarray.py index b196687af..10325050d 100644 --- a/numpy/core/multiarray.py +++ b/numpy/core/multiarray.py @@ -761,6 +761,7 @@ def dot(a, b, out=None): tensordot : Sum products over arbitrary axes. einsum : Einstein summation convention. matmul : '@' operator as method with out parameter. + linalg.multi_dot : Chained dot product. Examples -------- diff --git a/numpy/core/numeric.py b/numpy/core/numeric.py index 05f0b7820..e77161f59 100644 --- a/numpy/core/numeric.py +++ b/numpy/core/numeric.py @@ -310,7 +310,8 @@ def full(shape, fill_value, dtype=None, order='C'): """ if dtype is None: - dtype = array(fill_value).dtype + fill_value = asarray(fill_value) + dtype = fill_value.dtype a = empty(shape, dtype, order) multiarray.copyto(a, fill_value, casting='unsafe') return a diff --git a/numpy/core/numerictypes.py b/numpy/core/numerictypes.py index aac741612..2a015f48f 100644 --- a/numpy/core/numerictypes.py +++ b/numpy/core/numerictypes.py @@ -91,7 +91,7 @@ from numpy.core.multiarray import ( from numpy.core.overrides import set_module # we add more at the bottom -__all__ = ['sctypeDict', 'sctypeNA', 'typeDict', 'typeNA', 'sctypes', +__all__ = ['sctypeDict', 'typeDict', 'sctypes', 'ScalarType', 'obj2sctype', 'cast', 'nbytes', 'sctype2char', 'maximum_sctype', 'issctype', 'typecodes', 'find_common_type', 'issubdtype', 'datetime_data', 'datetime_as_string', @@ -106,7 +106,6 @@ from ._string_helpers import ( from ._type_aliases import ( sctypeDict, - sctypeNA, allTypes, bitname, sctypes, @@ -512,7 +511,6 @@ typecodes = {'Character':'c', # backwards compatibility --- deprecated name typeDict = sctypeDict -typeNA = sctypeNA # b -> boolean # u -> unsigned integer diff --git a/numpy/core/overrides.py b/numpy/core/overrides.py index 55c7bd1ea..816b11293 100644 --- a/numpy/core/overrides.py +++ b/numpy/core/overrides.py @@ -20,8 +20,8 @@ add_docstring( All arguments are required, and can only be passed by position. - Arguments - --------- + Parameters + ---------- implementation : function Function that implements the operation on NumPy array without overrides when called like ``implementation(*args, **kwargs)``. diff --git a/numpy/core/records.py b/numpy/core/records.py index 464615450..0d3fd9118 100644 --- a/numpy/core/records.py +++ b/numpy/core/records.py @@ -954,7 +954,86 @@ def fromfile(fd, dtype=None, shape=None, offset=0, formats=None, def array(obj, dtype=None, shape=None, offset=0, strides=None, formats=None, names=None, titles=None, aligned=False, byteorder=None, copy=True): - """Construct a record array from a wide-variety of objects. + """ + Construct a record array from a wide-variety of objects. + + A general-purpose record array constructor that dispatches to the + appropriate `recarray` creation function based on the inputs (see Notes). + + Parameters + ---------- + obj: any + Input object. See Notes for details on how various input types are + treated. + dtype: data-type, optional + Valid dtype for array. + shape: int or tuple of ints, optional + Shape of each array. + offset: int, optional + Position in the file or buffer to start reading from. + strides: tuple of ints, optional + Buffer (`buf`) is interpreted according to these strides (strides + define how many bytes each array element, row, column, etc. + occupy in memory). + formats, names, titles, aligned, byteorder : + If `dtype` is ``None``, these arguments are passed to + `numpy.format_parser` to construct a dtype. See that function for + detailed documentation. + copy: bool, optional + Whether to copy the input object (True), or to use a reference instead. + This option only applies when the input is an ndarray or recarray. + Defaults to True. + + Returns + ------- + np.recarray + Record array created from the specified object. + + Notes + ----- + If `obj` is ``None``, then call the `~numpy.recarray` constructor. If + `obj` is a string, then call the `fromstring` constructor. If `obj` is a + list or a tuple, then if the first object is an `~numpy.ndarray`, call + `fromarrays`, otherwise call `fromrecords`. If `obj` is a + `~numpy.recarray`, then make a copy of the data in the recarray + (if ``copy=True``) and use the new formats, names, and titles. If `obj` + is a file, then call `fromfile`. Finally, if obj is an `ndarray`, then + return ``obj.view(recarray)``, making a copy of the data if ``copy=True``. + + Examples + -------- + >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + array([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]]) + + >>> np.core.records.array(a) + rec.array([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]], + dtype=int32) + + >>> b = [(1, 1), (2, 4), (3, 9)] + >>> c = np.core.records.array(b, formats = ['i2', 'f2'], names = ('x', 'y')) + >>> c + rec.array([(1, 1.0), (2, 4.0), (3, 9.0)], + dtype=[('x', '<i2'), ('y', '<f2')]) + + >>> c.x + rec.array([1, 2, 3], dtype=int16) + + >>> c.y + rec.array([ 1.0, 4.0, 9.0], dtype=float16) + + >>> r = np.rec.array(['abc','def'], names=['col1','col2']) + >>> print(r.col1) + abc + + >>> r.col1 + array('abc', dtype='<U3') + + >>> r.col2 + array('def', dtype='<U3') """ if ((isinstance(obj, (type(None), str)) or isfileobj(obj)) and diff --git a/numpy/core/setup.py b/numpy/core/setup.py index 16bac4272..aede12080 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -738,6 +738,8 @@ def configuration(parent_package='',top_path=None): join('src', 'common', 'ufunc_override.h'), join('src', 'common', 'umathmodule.h'), join('src', 'common', 'numpyos.h'), + join('src', 'common', 'npy_cpu_dispatch.h'), + join('src', 'common', 'simd', 'simd.h'), ] common_src = [ @@ -773,9 +775,11 @@ def configuration(parent_package='',top_path=None): ####################################################################### multiarray_deps = [ + join('src', 'multiarray', 'abstractdtypes.h'), join('src', 'multiarray', 'arrayobject.h'), join('src', 'multiarray', 'arraytypes.h'), join('src', 'multiarray', 'arrayfunction_override.h'), + join('src', 'multiarray', 'array_coercion.h'), join('src', 'multiarray', 'npy_buffer.h'), join('src', 'multiarray', 'calculation.h'), join('src', 'multiarray', 'common.h'), @@ -824,9 +828,11 @@ def configuration(parent_package='',top_path=None): ] + npysort_sources + npymath_sources multiarray_src = [ + join('src', 'multiarray', 'abstractdtypes.c'), join('src', 'multiarray', 'alloc.c'), join('src', 'multiarray', 'arrayobject.c'), join('src', 'multiarray', 'arraytypes.c.src'), + join('src', 'multiarray', 'array_coercion.c'), join('src', 'multiarray', 'array_assign_scalar.c'), join('src', 'multiarray', 'array_assign_array.c'), join('src', 'multiarray', 'arrayfunction_override.c'), @@ -939,8 +945,11 @@ def configuration(parent_package='',top_path=None): # umath_tests module # ####################################################################### - config.add_extension('_umath_tests', - sources=[join('src', 'umath', '_umath_tests.c.src')]) + config.add_extension('_umath_tests', sources=[ + join('src', 'umath', '_umath_tests.c.src'), + join('src', 'umath', '_umath_tests.dispatch.c'), + join('src', 'common', 'npy_cpu_features.c.src'), + ]) ####################################################################### # custom rational dtype module # @@ -967,6 +976,7 @@ def configuration(parent_package='',top_path=None): config.add_subpackage('tests') config.add_data_dir('tests/data') config.add_data_dir('tests/examples') + config.add_data_files('*.pyi') config.make_svn_version_py() diff --git a/numpy/core/setup_common.py b/numpy/core/setup_common.py index 8c0149497..f15425c87 100644 --- a/numpy/core/setup_common.py +++ b/numpy/core/setup_common.py @@ -178,6 +178,9 @@ OPTIONAL_FUNCTION_ATTRIBUTES = [('__attribute__((optimize("unroll-loops")))', # gcc 4.8.4 support attributes but not with intrisics # tested via "#include<%s> int %s %s(void *){code; return 0;};" % (header, attribute, name, code) # function name will be converted to HAVE_<upper-case-name> preprocessor macro +# The _mm512_castps_si512 instruction is specific check for AVX-512F support +# in gcc-4.9 which is missing a subset of intrinsics. See +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61878 OPTIONAL_FUNCTION_ATTRIBUTES_WITH_INTRINSICS = [('__attribute__((target("avx2,fma")))', 'attribute_target_avx2_with_intrinsics', '__m256 temp = _mm256_set1_ps(1.0); temp = \ @@ -185,11 +188,12 @@ OPTIONAL_FUNCTION_ATTRIBUTES_WITH_INTRINSICS = [('__attribute__((target("avx2,fm 'immintrin.h'), ('__attribute__((target("avx512f")))', 'attribute_target_avx512f_with_intrinsics', - '__m512 temp = _mm512_set1_ps(1.0)', + '__m512i temp = _mm512_castps_si512(_mm512_set1_ps(1.0))', 'immintrin.h'), ('__attribute__((target ("avx512f,avx512dq,avx512bw,avx512vl,avx512cd")))', 'attribute_target_avx512_skx_with_intrinsics', '__mmask8 temp = _mm512_fpclass_pd_mask(_mm512_set1_pd(1.0), 0x01);\ + __m512i temp = _mm512_castps_si512(_mm512_set1_ps(1.0));\ _mm_mask_storeu_epi8(NULL, 0xFF, _mm_broadcastmb_epi64(temp))', 'immintrin.h'), ] diff --git a/numpy/core/src/common/npy_config.h b/numpy/core/src/common/npy_config.h index aebe241a5..27328aa73 100644 --- a/numpy/core/src/common/npy_config.h +++ b/numpy/core/src/common/npy_config.h @@ -3,11 +3,12 @@ #include "config.h" #include "npy_cpu_features.h" +#include "npy_cpu_dispatch.h" #include "numpy/numpyconfig.h" #include "numpy/npy_cpu.h" #include "numpy/npy_os.h" -/* blacklist */ +/* blocklist */ /* Disable broken Sun Workshop Pro math functions */ #ifdef __SUNPRO_C diff --git a/numpy/core/src/common/npy_cpu_dispatch.h b/numpy/core/src/common/npy_cpu_dispatch.h new file mode 100644 index 000000000..846d1ebb9 --- /dev/null +++ b/numpy/core/src/common/npy_cpu_dispatch.h @@ -0,0 +1,260 @@ +#ifndef NPY_CPU_DISPATCH_H_ +#define NPY_CPU_DISPATCH_H_ +/** + * This file is part of the NumPy CPU dispatcher. Please have a look at doc/reference/simd-optimizations.html + * To get a better understanding of the mechanism behind it. + */ +#include "npy_cpu_features.h" // NPY_CPU_HAVE +#include "numpy/utils.h" // NPY_EXPAND, NPY_CAT +/** + * Bringing the main configration header '_cpu_dispatch.h'. + * + * This header is generated by the distutils module 'ccompiler_opt', + * and contains all the #definitions and headers of instruction-sets, + * that had been configured through command arguments '--cpu-baseline' and '--cpu-dispatch'. + * + * It also contains extra C #definitions and macros that are used for implementing + * NumPy module's attributes `__cpu_baseline__` and `__cpu_dispaٍtch__`. + */ +/** + * Note: Always gaurd the genreated headers within 'NPY_DISABLE_OPTIMIZATION', + * due the nature of command argument '--disable-optimization', + * which is explicitly disabling the module ccompiler_opt. + */ +#ifndef NPY_DISABLE_OPTIMIZATION + #if defined(__powerpc64__) && !defined(__cplusplus) && defined(bool) + /** + * "altivec.h" header contains the definitions(bool, vector, pixel), + * usually in c++ we undefine them after including the header. + * It's better anyway to take them off and use built-in types(__vector, __pixel, __bool) instead, + * since c99 supports bool variables which may lead to ambiguous errors. + */ + // backup 'bool' before including '_cpu_dispatch.h', since it may not defiend as a compiler token. + #define NPY__DISPATCH_DEFBOOL + typedef bool npy__dispatch_bkbool; + #endif + #include "_cpu_dispatch.h" + #ifdef NPY_HAVE_VSX + #undef bool + #undef vector + #undef pixel + #ifdef NPY__DISPATCH_DEFBOOL + #define bool npy__dispatch_bkbool + #endif + #endif +#endif // !NPY_DISABLE_OPTIMIZATION +/** + * Macro NPY_CPU_DISPATCH_CURFX(NAME) + * + * Returns @NAME suffixed with "_" + "the current target" during compiling + * the wrapped sources that generated from the dispatch-able sources according + * to the provided configuration statements. + * + * It also returns @NAME as-is without any suffix when it comes to the baseline or + * in case if the optimization is disabled. + * + * The idea behind this Macro is to allow exporting certain symbols and to + * avoid linking duplications due to the nature of the dispatch-able sources. + * + * Example: + * @targets baseline avx avx512_skx vsx3 asimdhp // configration statments + * + * void NPY_CPU_DISPATCH_CURFX(dispatch_me)(const int *src, int *dst) + * { + * // the kernel + * } + * + * By assuming the required optimizations are enabled via '--cpu-dspatch' and + * the compiler supported them too, then the generated symbols will be named as follows: + * + * - x86: + * dispatch_me(const int*, int*) // baseline + * dispatch_me_AVX(const int*, int*) + * dispatch_me_AVX512_SKX(const int*, int*) + * + * - ppc64: + * dispatch_me(const int*, int*) + * dispatch_me_VSX3(const int*, int*) + * + * - ARM: + * dispatch_me(const int*, int*) + * dispatch_me_ASIMHP(const int*, int*) + * + * - unsupported arch or when optimization is disabled: + * dispatch_me(const int*, int*) + * + * For forward declarations, see 'NPY_CPU_DISPATCH_DECLARE'. + */ +#ifdef NPY__CPU_TARGET_CURRENT + // 'NPY__CPU_TARGET_CURRENT': only defined by the dispatch-able sources + #define NPY_CPU_DISPATCH_CURFX(NAME) NPY_CAT(NPY_CAT(NAME, _), NPY__CPU_TARGET_CURRENT) +#else + #define NPY_CPU_DISPATCH_CURFX(NAME) NPY_EXPAND(NAME) +#endif +/** + * Defining the default behavior for the configurable macros of dispatch-able sources, + * 'NPY__CPU_DISPATCH_CALL(...)' and 'NPY__CPU_DISPATCH_BASELINE_CALL(...)' + * + * These macros are defined inside the generated config files that been derived from + * the configuration statements of the dispatch-able sources. + * + * The generated config file takes the same name of the dispatch-able source with replacing + * the extension to '.h' instead of '.c', and it should be treated as a header template. + * + * For more clarification, please have a look at doc/reference/simd-optimizations.html. + */ +#ifndef NPY_DISABLE_OPTIMIZATION + #define NPY__CPU_DISPATCH_BASELINE_CALL(CB, ...) \ + &&"Expected config header of the dispatch-able source"; + #define NPY__CPU_DISPATCH_CALL(CHK, CB, ...) \ + &&"Expected config header of the dispatch-able source"; +#else + /** + * We assume by default that all configuration statements contains 'baseline' option however, + * if the dispatch-able source doesn't require it, then the dispatch-able source and following macros + * need to be guard it with '#ifndef NPY_DISABLE_OPTIMIZATION' + */ + #define NPY__CPU_DISPATCH_BASELINE_CALL(CB, ...) \ + NPY_EXPAND(CB(__VA_ARGS__)) + #define NPY__CPU_DISPATCH_CALL(CHK, CB, ...) +#endif // !NPY_DISABLE_OPTIMIZATION +/** + * Macro NPY_CPU_DISPATCH_DECLARE(LEFT, ...) is used to provide forward + * declarations for the exported variables and functions that defined inside + * the dispatch-able sources. + * + * The first argument should ends with the exported function or variable name, + * while the Macro pasting the extra arguments. + * + * Examples: + * #ifndef NPY_DISABLE_OPTIMIZATION + * #include "dispatchable_source_name.dispatch.h" + * #endif + * + * NPY_CPU_DISPATCH_DECLARE(void dispatch_me, (const int*, int*)) + * NPY_CPU_DISPATCH_DECLARE(extern cb_type callback_tab, [TAB_SIZE]) + * + * By assuming the provided config header drived from a dispatch-able source, + * that configured with "@targets baseline sse41 vsx3 asimdhp", + * they supported by the compiler and enabled via '--cpu-dspatch', + * then the prototype declrations at the above example will equlivent to the follows: + * + * - x86: + * void dispatch_me(const int*, int*); // baseline + * void dispatch_me_SSE41(const int*, int*); + * + * extern cb_type callback_tab[TAB_SIZE]; + * extern cb_type callback_tab_SSE41[TAB_SIZE]; + * + * - ppc64: + * void dispatch_me(const int*, int*); + * void dispatch_me_VSX3(const int*, int*); + * + * extern cb_type callback_tab[TAB_SIZE]; + * extern cb_type callback_tab_VSX3[TAB_SIZE]; + * + * - ARM: + * void dispatch_me(const int*, int*); + * void dispatch_me_ASIMDHP(const int*, int*); + * + * extern cb_type callback_tab[TAB_SIZE]; + * extern cb_type callback_tab_ASIMDHP[TAB_SIZE]; + * + * - unsupported arch or when optimization is disabled: + * void dispatch_me(const int*, int*); + * extern cb_type callback_tab[TAB_SIZE]; + * + * For runtime dispatching, see 'NPY_CPU_DISPATCH_CALL' + */ +#define NPY_CPU_DISPATCH_DECLARE(...) \ + NPY__CPU_DISPATCH_CALL(NPY_CPU_DISPATCH_DECLARE_CHK_, NPY_CPU_DISPATCH_DECLARE_CB_, __VA_ARGS__) \ + NPY__CPU_DISPATCH_BASELINE_CALL(NPY_CPU_DISPATCH_DECLARE_BASE_CB_, __VA_ARGS__) +// Preprocessor callbacks +#define NPY_CPU_DISPATCH_DECLARE_CB_(DUMMY, TARGET_NAME, LEFT, ...) \ + NPY_CAT(NPY_CAT(LEFT, _), TARGET_NAME) __VA_ARGS__; +#define NPY_CPU_DISPATCH_DECLARE_BASE_CB_(LEFT, ...) \ + LEFT __VA_ARGS__; +// Dummy CPU runtime checking +#define NPY_CPU_DISPATCH_DECLARE_CHK_(FEATURE) +/** + * Macro NPY_CPU_DISPATCH_DECLARE_XB(LEFT, ...) + * + * Same as `NPY_CPU_DISPATCH_DECLARE` but exclude the baseline declration even + * if it was provided within the configration statments. + */ +#define NPY_CPU_DISPATCH_DECLARE_XB(...) \ + NPY__CPU_DISPATCH_CALL(NPY_CPU_DISPATCH_DECLARE_CHK_, NPY_CPU_DISPATCH_DECLARE_CB_, __VA_ARGS__) +/** + * Macro NPY_CPU_DISPATCH_CALL(LEFT, ...) is used for runtime dispatching + * of the exported functions and variables within the dispatch-able sources + * according to the highested interesed CPU features that supported by the + * running machine depending on the required optimizations. + * + * The first argument should ends with the exported function or variable name, + * while the Macro pasting the extra arguments. + * + * Example: + * Assume we have a dispatch-able source exporting the following function: + * + * @targets baseline avx2 avx512_skx // configration statments + * + * void NPY_CPU_DISPATCH_CURFX(dispatch_me)(const int *src, int *dst) + * { + * // the kernel + * } + * + * In order to call or to assign the pointer of it from outside the dispatch-able source, + * you have to use this Macro as follows: + * + * // bring the genreated config header of the dispatch-abel source + * #ifndef NPY_DISABLE_OPTIMIZATION + * #include "dispatchable_source_name.dispatch.h" + * #endif + * // forward declaration + * NPY_CPU_DISPATCH_DECLARE(dispatch_me, (const int *src, int *dst)) + * + * typedef void(*func_type)(const int*, int*); + * func_type the_callee(const int *src, int *dst, func_type *cb) + * { + * // direct call + * NPY_CPU_DISPATCH_CALL(dispatch_me, (src, dst)) + * // assign the pointer + * NPY_CPU_DISPATCH_CALL(*cb = dispatch_me) + * // return the pointer + * NPY_CPU_DISPATCH_CALL(return dispatch_me) + * } + */ +#define NPY_CPU_DISPATCH_CALL(...) \ + if (0) {/*DUMMY*/} \ + NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, NPY_CPU_DISPATCH_CALL_CB_, __VA_ARGS__) \ + NPY__CPU_DISPATCH_BASELINE_CALL(NPY_CPU_DISPATCH_CALL_BASE_CB_, __VA_ARGS__) +// Preprocessor callbacks +#define NPY_CPU_DISPATCH_CALL_CB_(TESTED_FEATURES, TARGET_NAME, LEFT, ...) \ + else if (TESTED_FEATURES) { NPY_CAT(NPY_CAT(LEFT, _), TARGET_NAME) __VA_ARGS__; } +#define NPY_CPU_DISPATCH_CALL_BASE_CB_(LEFT, ...) \ + else { LEFT __VA_ARGS__; } +/** + * Macro NPY_CPU_DISPATCH_CALL_XB(LEFT, ...) + * + * Same as `NPY_CPU_DISPATCH_DECLARE` but exclude the baseline declration even + * if it was provided within the configration statments. + */ +#define NPY_CPU_DISPATCH_CALL_XB(...) \ + if (0) {/*DUMMY*/} \ + NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, NPY_CPU_DISPATCH_CALL_CB_, __VA_ARGS__) +/** + * Macro NPY_CPU_DISPATCH_CALL_ALL(LEFT, ...) + * + * Same as `NPY_CPU_DISPATCH_CALL` but dispatching all the required optimizations for + * the exported functions and variables instead of highest interested one. + */ +#define NPY_CPU_DISPATCH_CALL_ALL(...) \ + NPY__CPU_DISPATCH_CALL(NPY_CPU_HAVE, NPY_CPU_DISPATCH_CALL_ALL_CB_, __VA_ARGS__) \ + NPY__CPU_DISPATCH_BASELINE_CALL(NPY_CPU_DISPATCH_CALL_ALL_BASE_CB_, __VA_ARGS__) +// Preprocessor callbacks +#define NPY_CPU_DISPATCH_CALL_ALL_CB_(TESTED_FEATURES, TARGET_NAME, LEFT, ...) \ + if (TESTED_FEATURES) { NPY_CAT(NPY_CAT(LEFT, _), TARGET_NAME) __VA_ARGS__; } +#define NPY_CPU_DISPATCH_CALL_ALL_BASE_CB_(LEFT, ...) \ + { LEFT __VA_ARGS__; } + +#endif // NPY_CPU_DISPATCH_H_ diff --git a/numpy/core/src/common/npy_cpu_features.c.src b/numpy/core/src/common/npy_cpu_features.c.src index bd4743905..dfcf98c74 100644 --- a/numpy/core/src/common/npy_cpu_features.c.src +++ b/numpy/core/src/common/npy_cpu_features.c.src @@ -1,6 +1,7 @@ #include "npy_cpu_features.h" +#include "npy_cpu_dispatch.h" // To guarantee the CPU baseline definitions are in scope. #include "numpy/npy_common.h" // for NPY_INLINE -#include "numpy/npy_cpu.h" // To guarantee of having CPU definitions in scope. +#include "numpy/npy_cpu.h" // To guarantee the CPU definitions are in scope. /******************** Private Definitions *********************/ @@ -12,6 +13,18 @@ static unsigned char npy__cpu_have[NPY_CPU_FEATURE_MAX]; // Almost detect all CPU features in runtime static void npy__cpu_init_features(void); +/* + * Disable CPU dispatched features at runtime if environment variable + * 'NPY_DISABLE_CPU_FEATURES' is defined. + * Multiple features can be present, and separated by space, comma, or tab. + * Raises an error if parsing fails or if the feature was not enabled +*/ +static void +npy__cpu_try_disable_env(void); + +/* Ensure the build's CPU baseline features are supported at runtime */ +static void +npy__cpu_validate_baseline(void); /******************** Public Definitions *********************/ @@ -27,6 +40,11 @@ NPY_VISIBILITY_HIDDEN int npy_cpu_init(void) { npy__cpu_init_features(); + npy__cpu_validate_baseline(); + npy__cpu_try_disable_env(); + + if (PyErr_Occurred()) + return -1; return 0; } @@ -55,6 +73,201 @@ npy_cpu_features_dict(void) return dict; } +#define NPY__CPU_PYLIST_APPEND_CB(FEATURE, LIST) \ + item = PyUnicode_FromString(NPY_TOSTRING(FEATURE)); \ + if (item == NULL) { \ + Py_DECREF(LIST); \ + return NULL; \ + } \ + PyList_SET_ITEM(LIST, index++, item); + +NPY_VISIBILITY_HIDDEN PyObject * +npy_cpu_baseline_list(void) +{ +#if !defined(NPY_DISABLE_OPTIMIZATION) && NPY_WITH_CPU_BASELINE_N > 0 + PyObject *list = PyList_New(NPY_WITH_CPU_BASELINE_N), *item; + int index = 0; + if (list != NULL) { + NPY_WITH_CPU_BASELINE_CALL(NPY__CPU_PYLIST_APPEND_CB, list) + } + return list; +#else + return PyList_New(0); +#endif +} + +NPY_VISIBILITY_HIDDEN PyObject * +npy_cpu_dispatch_list(void) +{ +#if !defined(NPY_DISABLE_OPTIMIZATION) && NPY_WITH_CPU_DISPATCH_N > 0 + PyObject *list = PyList_New(NPY_WITH_CPU_DISPATCH_N), *item; + int index = 0; + if (list != NULL) { + NPY_WITH_CPU_DISPATCH_CALL(NPY__CPU_PYLIST_APPEND_CB, list) + } + return list; +#else + return PyList_New(0); +#endif +} + +/******************** Private Definitions *********************/ +#define NPY__CPU_FEATURE_ID_CB(FEATURE, WITH_FEATURE) \ + if (strcmp(NPY_TOSTRING(FEATURE), WITH_FEATURE) == 0) \ + return NPY_CAT(NPY_CPU_FEATURE_, FEATURE); +/** + * Returns CPU feature's ID, if the 'feature' was part of baseline + * features that had been configured via --cpu-baseline + * otherwise it returns 0 +*/ +static NPY_INLINE int +npy__cpu_baseline_fid(const char *feature) +{ +#if !defined(NPY_DISABLE_OPTIMIZATION) && NPY_WITH_CPU_BASELINE_N > 0 + NPY_WITH_CPU_BASELINE_CALL(NPY__CPU_FEATURE_ID_CB, feature) +#endif + return 0; +} +/** + * Returns CPU feature's ID, if the 'feature' was part of dispatched + * features that had been configured via --cpu-dispatch + * otherwise it returns 0 +*/ +static NPY_INLINE int +npy__cpu_dispatch_fid(const char *feature) +{ +#if !defined(NPY_DISABLE_OPTIMIZATION) && NPY_WITH_CPU_DISPATCH_N > 0 + NPY_WITH_CPU_DISPATCH_CALL(NPY__CPU_FEATURE_ID_CB, feature) +#endif + return 0; +} + +static void +npy__cpu_validate_baseline(void) +{ +#if !defined(NPY_DISABLE_OPTIMIZATION) && NPY_WITH_CPU_BASELINE_N > 0 + char baseline_failure[sizeof(NPY_WITH_CPU_BASELINE) + 1]; + char *fptr = &baseline_failure[0]; + + #define NPY__CPU_VALIDATE_CB(FEATURE, DUMMY) \ + if (!npy__cpu_have[NPY_CAT(NPY_CPU_FEATURE_, FEATURE)]) { \ + const int size = sizeof(NPY_TOSTRING(FEATURE)); \ + memcpy(fptr, NPY_TOSTRING(FEATURE), size); \ + fptr[size] = ' '; fptr += size + 1; \ + } + NPY_WITH_CPU_BASELINE_CALL(NPY__CPU_VALIDATE_CB, DUMMY) // extra arg for msvc + *fptr = '\0'; + + if (baseline_failure[0] != '\0') { + *(fptr-1) = '\0'; // trim the last space + PyErr_Format(PyExc_RuntimeError, + "NumPy was built with baseline optimizations: \n" + "(" NPY_WITH_CPU_BASELINE ") but your machine doesn't support:\n(%s).", + baseline_failure + ); + } +#endif +} + +static void +npy__cpu_try_disable_env(void) +{ + char *disenv = getenv("NPY_DISABLE_CPU_FEATURES"); + if (disenv == NULL || disenv[0] == 0) { + return; + } + #define NPY__CPU_ENV_ERR_HEAD \ + "During parsing environment variable 'NPY_DISABLE_CPU_FEATURES':\n" + +#if !defined(NPY_DISABLE_OPTIMIZATION) && NPY_WITH_CPU_DISPATCH_N > 0 + #define NPY__MAX_VAR_LEN 1024 // More than enough for this era + size_t var_len = strlen(disenv) + 1; + if (var_len > NPY__MAX_VAR_LEN) { + PyErr_Format(PyExc_RuntimeError, + "Length of environment variable 'NPY_DISABLE_CPU_FEATURES' is %d, only %d accepted", + var_len, NPY__MAX_VAR_LEN - 1 + ); + return; + } + char disable_features[NPY__MAX_VAR_LEN]; + memcpy(disable_features, disenv, var_len); + + char nexist[NPY__MAX_VAR_LEN]; + char *nexist_cur = &nexist[0]; + + char notsupp[sizeof(NPY_WITH_CPU_DISPATCH) + 1]; + char *notsupp_cur = ¬supp[0]; + + //comma and space including (htab, vtab, CR, LF, FF) + const char *delim = ", \t\v\r\n\f"; + char *feature = strtok(disable_features, delim); + while (feature) { + if (npy__cpu_baseline_fid(feature) > 0) { + PyErr_Format(PyExc_RuntimeError, + NPY__CPU_ENV_ERR_HEAD + "You cannot disable CPU feature '%s', since it is part of " + "the baseline optimizations:\n" + "(" NPY_WITH_CPU_BASELINE ").", + feature + ); + break; + } + // check if the feature is part of dispatched features + int feature_id = npy__cpu_dispatch_fid(feature); + if (feature_id == 0) { + int flen = strlen(feature); + memcpy(nexist_cur, feature, flen); + nexist_cur[flen] = ' '; nexist_cur += flen + 1; + goto next; + } + // check if the feature supported by the running machine + if (!npy__cpu_have[feature_id]) { + int flen = strlen(feature); + memcpy(notsupp_cur, feature, flen); + notsupp_cur[flen] = ' '; notsupp_cur += flen + 1; + goto next; + } + // Finaly we can disable it + npy__cpu_have[feature_id] = 0; + next: + feature = strtok(NULL, delim); + } + + *nexist_cur = '\0'; + if (nexist[0] != '\0') { + *(nexist_cur-1) = '\0'; // trim the last space + PyErr_WarnFormat(PyExc_RuntimeWarning, 1, + NPY__CPU_ENV_ERR_HEAD + "You cannot disable CPU features (%s), since " + "they are not part of the dispatched optimizations\n" + "(" NPY_WITH_CPU_DISPATCH ").", + nexist + ); + } + + *notsupp_cur = '\0'; + if (notsupp[0] != '\0') { + *(notsupp_cur-1) = '\0'; // trim the last space + PyErr_WarnFormat(PyExc_RuntimeWarning, 1, + NPY__CPU_ENV_ERR_HEAD + "You cannot disable CPU features (%s), since " + "they are not supported by your machine.", + notsupp + ); + } +#else + PyErr_WarnFormat(PyExc_RuntimeWarning, 1, + NPY__CPU_ENV_ERR_HEAD + "You cannot use environment variable 'NPY_DISABLE_CPU_FEATURES', since " + #ifdef NPY_DISABLE_OPTIMIZATION + "the NumPy library was compiled with optimization disabled." + #else + "the NumPy library was compiled without any dispatched optimizations." + #endif + ); +#endif +} + /**************************************************************** * This section is reserved to defining @npy__cpu_init_features * for each CPU architecture, please try to keep it clean. Ty @@ -283,28 +496,7 @@ npy__cpu_init_features_arm8(void) * so we play it safe */ #include <stdio.h> -#include <fcntl.h> - -#define NPY__HWCAP 16 -#define NPY__HWCAP2 26 - -// arch/arm/include/uapi/asm/hwcap.h -#define NPY__HWCAP_HALF (1 << 1) -#define NPY__HWCAP_NEON (1 << 12) -#define NPY__HWCAP_VFPv3 (1 << 13) -#define NPY__HWCAP_VFPv4 (1 << 16) -#define NPY__HWCAP2_AES (1 << 0) -#define NPY__HWCAP2_PMULL (1 << 1) -#define NPY__HWCAP2_SHA1 (1 << 2) -#define NPY__HWCAP2_SHA2 (1 << 3) -#define NPY__HWCAP2_CRC32 (1 << 4) -// arch/arm64/include/uapi/asm/hwcap.h -#define NPY__HWCAP_FP (1 << 0) -#define NPY__HWCAP_ASIMD (1 << 1) -#define NPY__HWCAP_FPHP (1 << 9) -#define NPY__HWCAP_ASIMDHP (1 << 10) -#define NPY__HWCAP_ASIMDDP (1 << 20) -#define NPY__HWCAP_ASIMDFHM (1 << 23) +#include "npy_cpuinfo_parser.h" __attribute__((weak)) unsigned long getauxval(unsigned long); // linker should handle it static int @@ -339,10 +531,12 @@ npy__cpu_init_features_linux(void) } if (hwcap == 0 && hwcap2 == 0) { /* - * FIXME: failback to compiler definitions, - * BTW we can parse /proc/cpuinfo for badly patched kernels + * try parsing with /proc/cpuinfo, if sandboxed + * failback to compiler definitions */ - return 0; + if(!get_feature_from_proc_cpuinfo(&hwcap, &hwcap2)) { + return 0; + } } #ifdef __arm__ // Detect Arm8 (aarch32 state) @@ -367,9 +561,10 @@ npy__cpu_init_features_linux(void) npy__cpu_init_features_arm8(); } else { npy__cpu_have[NPY_CPU_FEATURE_NEON] = (hwcap & NPY__HWCAP_NEON) != 0; - npy__cpu_have[NPY_CPU_FEATURE_NEON_FP16] = (hwcap & (NPY__HWCAP_NEON | NPY__HWCAP_VFPv3 | - NPY__HWCAP_HALF)) != 0; - npy__cpu_have[NPY_CPU_FEATURE_NEON_VFPV4] = (hwcap & (NPY__HWCAP_NEON | NPY__HWCAP_VFPv4)) != 0; + if (npy__cpu_have[NPY_CPU_FEATURE_NEON]) { + npy__cpu_have[NPY_CPU_FEATURE_NEON_FP16] = (hwcap & NPY__HWCAP_HALF) != 0; + npy__cpu_have[NPY_CPU_FEATURE_NEON_VFPV4] = (hwcap & NPY__HWCAP_VFPv4) != 0; + } } return 1; } @@ -384,7 +579,7 @@ npy__cpu_init_features(void) return; #endif // We have nothing else todo -#if defined(NPY_HAVE_NEON_ARM8) || defined(__aarch64__) || (defined(__ARM_ARCH) && __ARM_ARCH >= 8) +#if defined(NPY_HAVE_ASIMD) || defined(__aarch64__) || (defined(__ARM_ARCH) && __ARM_ARCH >= 8) #if defined(NPY_HAVE_FPHP) || defined(__ARM_FEATURE_FP16_VECTOR_ARITHMETIC) npy__cpu_have[NPY_CPU_FEATURE_FPHP] = 1; #endif @@ -416,5 +611,13 @@ npy__cpu_init_features(void) static void npy__cpu_init_features(void) { + /* + * just in case if the compiler doesn't respect ANSI + * but for knowing paltforms it still nessecery, because @npy__cpu_init_features + * may called multiple of times and we need to clear the disabled features by + * ENV Var or maybe in the future we can support other methods like + * global variables, go back to @npy__cpu_try_disable_env for more understanding + */ + memset(npy__cpu_have, 0, sizeof(npy__cpu_have[0]) * NPY_CPU_FEATURE_MAX); } #endif diff --git a/numpy/core/src/common/npy_cpu_features.h b/numpy/core/src/common/npy_cpu_features.h index 0e8901328..693a9857d 100644 --- a/numpy/core/src/common/npy_cpu_features.h +++ b/numpy/core/src/common/npy_cpu_features.h @@ -87,8 +87,20 @@ enum npy_cpu_features /* * Initialize CPU features + * + * This function + * - detects runtime CPU features + * - check that baseline CPU features are present + * - uses 'NPY_DISABLE_CPU_FEATURES' to disable dispatchable features + * + * It will set a RuntimeError when + * - CPU baseline features from the build are not supported at runtime + * - 'NPY_DISABLE_CPU_FEATURES' tries to disable a baseline feature + * and will warn if 'NPY_DISABLE_CPU_FEATURES' tries to disable a feature that + * is not disabled (the machine or build does not support it, or the project was + * not built with any feature optimization support) * return 0 on success otherwise return -1 -*/ + */ NPY_VISIBILITY_HIDDEN int npy_cpu_init(void); @@ -109,6 +121,48 @@ npy_cpu_have(NPY_CPU_FEATURE_##FEATURE_NAME) */ NPY_VISIBILITY_HIDDEN PyObject * npy_cpu_features_dict(void); +/* + * Return a new a Python list contains the minimal set of required optimizations + * that supported by the compiler and platform according to the specified + * values to command argument '--cpu-baseline'. + * + * This function is mainly used to implement umath's attrbute '__cpu_baseline__', + * and the items are sorted from the lowest to highest interest. + * + * For example, according to the default build configuration and by assuming the compiler + * support all the involved optimizations then the returned list should equivalent to: + * + * On x86: ['SSE', 'SSE2'] + * On x64: ['SSE', 'SSE2', 'SSE3'] + * On armhf: [] + * On aarch64: ['NEON', 'NEON_FP16', 'NEON_VPFV4', 'ASIMD'] + * On ppc64: [] + * On ppc64le: ['VSX', 'VSX2'] + * On any other arch or if the optimization is disabled: [] + */ +NPY_VISIBILITY_HIDDEN PyObject * +npy_cpu_baseline_list(void); +/* + * Return a new a Python list contains the dispatched set of additional optimizations + * that supported by the compiler and platform according to the specified + * values to command argument '--cpu-dispatch'. + * + * This function is mainly used to implement umath's attrbute '__cpu_dispatch__', + * and the items are sorted from the lowest to highest interest. + * + * For example, according to the default build configuration and by assuming the compiler + * support all the involved optimizations then the returned list should equivalent to: + * + * On x86: ['SSE3', 'SSSE3', 'SSE41', 'POPCNT', 'SSE42', 'AVX', 'F16C', 'FMA3', 'AVX2', 'AVX512F', ...] + * On x64: ['SSSE3', 'SSE41', 'POPCNT', 'SSE42', 'AVX', 'F16C', 'FMA3', 'AVX2', 'AVX512F', ...] + * On armhf: ['NEON', 'NEON_FP16', 'NEON_VPFV4', 'ASIMD', 'ASIMDHP', 'ASIMDDP', 'ASIMDFHM'] + * On aarch64: ['ASIMDHP', 'ASIMDDP', 'ASIMDFHM'] + * On ppc64: ['VSX', 'VSX2', 'VSX3'] + * On ppc64le: ['VSX3'] + * On any other arch or if the optimization is disabled: [] + */ +NPY_VISIBILITY_HIDDEN PyObject * +npy_cpu_dispatch_list(void); #ifdef __cplusplus } diff --git a/numpy/core/src/common/npy_cpuinfo_parser.h b/numpy/core/src/common/npy_cpuinfo_parser.h new file mode 100644 index 000000000..4c00c847b --- /dev/null +++ b/numpy/core/src/common/npy_cpuinfo_parser.h @@ -0,0 +1,262 @@ +/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#ifndef __NPY_CPUINFO_PARSER_H__
+#define __NPY_CPUINFO_PARSER_H__
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stddef.h>
+
+#define NPY__HWCAP 16
+#define NPY__HWCAP2 26
+
+// arch/arm/include/uapi/asm/hwcap.h
+#define NPY__HWCAP_HALF (1 << 1)
+#define NPY__HWCAP_NEON (1 << 12)
+#define NPY__HWCAP_VFPv3 (1 << 13)
+#define NPY__HWCAP_VFPv4 (1 << 16)
+#define NPY__HWCAP2_AES (1 << 0)
+#define NPY__HWCAP2_PMULL (1 << 1)
+#define NPY__HWCAP2_SHA1 (1 << 2)
+#define NPY__HWCAP2_SHA2 (1 << 3)
+#define NPY__HWCAP2_CRC32 (1 << 4)
+// arch/arm64/include/uapi/asm/hwcap.h
+#define NPY__HWCAP_FP (1 << 0)
+#define NPY__HWCAP_ASIMD (1 << 1)
+#define NPY__HWCAP_FPHP (1 << 9)
+#define NPY__HWCAP_ASIMDHP (1 << 10)
+#define NPY__HWCAP_ASIMDDP (1 << 20)
+#define NPY__HWCAP_ASIMDFHM (1 << 23)
+/*
+ * Get the size of a file by reading it until the end. This is needed
+ * because files under /proc do not always return a valid size when
+ * using fseek(0, SEEK_END) + ftell(). Nor can they be mmap()-ed.
+ */
+static int
+get_file_size(const char* pathname)
+{
+ int fd, result = 0;
+ char buffer[256];
+
+ fd = open(pathname, O_RDONLY);
+ if (fd < 0) {
+ return -1;
+ }
+
+ for (;;) {
+ int ret = read(fd, buffer, sizeof buffer);
+ if (ret < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ break;
+ }
+ if (ret == 0) {
+ break;
+ }
+ result += ret;
+ }
+ close(fd);
+ return result;
+}
+
+/*
+ * Read the content of /proc/cpuinfo into a user-provided buffer.
+ * Return the length of the data, or -1 on error. Does *not*
+ * zero-terminate the content. Will not read more
+ * than 'buffsize' bytes.
+ */
+static int
+read_file(const char* pathname, char* buffer, size_t buffsize)
+{
+ int fd, count;
+
+ fd = open(pathname, O_RDONLY);
+ if (fd < 0) {
+ return -1;
+ }
+ count = 0;
+ while (count < (int)buffsize) {
+ int ret = read(fd, buffer + count, buffsize - count);
+ if (ret < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ if (count == 0) {
+ count = -1;
+ }
+ break;
+ }
+ if (ret == 0) {
+ break;
+ }
+ count += ret;
+ }
+ close(fd);
+ return count;
+}
+
+/*
+ * Extract the content of a the first occurence of a given field in
+ * the content of /proc/cpuinfo and return it as a heap-allocated
+ * string that must be freed by the caller.
+ *
+ * Return NULL if not found
+ */
+static char*
+extract_cpuinfo_field(const char* buffer, int buflen, const char* field)
+{
+ int fieldlen = strlen(field);
+ const char* bufend = buffer + buflen;
+ char* result = NULL;
+ int len;
+ const char *p, *q;
+
+ /* Look for first field occurence, and ensures it starts the line. */
+ p = buffer;
+ for (;;) {
+ p = memmem(p, bufend-p, field, fieldlen);
+ if (p == NULL) {
+ goto EXIT;
+ }
+
+ if (p == buffer || p[-1] == '\n') {
+ break;
+ }
+
+ p += fieldlen;
+ }
+
+ /* Skip to the first column followed by a space */
+ p += fieldlen;
+ p = memchr(p, ':', bufend-p);
+ if (p == NULL || p[1] != ' ') {
+ goto EXIT;
+ }
+
+ /* Find the end of the line */
+ p += 2;
+ q = memchr(p, '\n', bufend-p);
+ if (q == NULL) {
+ q = bufend;
+ }
+
+ /* Copy the line into a heap-allocated buffer */
+ len = q - p;
+ result = malloc(len + 1);
+ if (result == NULL) {
+ goto EXIT;
+ }
+
+ memcpy(result, p, len);
+ result[len] = '\0';
+
+EXIT:
+ return result;
+}
+
+/*
+ * Checks that a space-separated list of items contains one given 'item'.
+ * Returns 1 if found, 0 otherwise.
+ */
+static int
+has_list_item(const char* list, const char* item)
+{
+ const char* p = list;
+ int itemlen = strlen(item);
+
+ if (list == NULL) {
+ return 0;
+ }
+
+ while (*p) {
+ const char* q;
+
+ /* skip spaces */
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+
+ /* find end of current list item */
+ q = p;
+ while (*q && *q != ' ' && *q != '\t') {
+ q++;
+ }
+
+ if (itemlen == q-p && !memcmp(p, item, itemlen)) {
+ return 1;
+ }
+
+ /* skip to next item */
+ p = q;
+ }
+ return 0;
+}
+
+static void setHwcap(char* cpuFeatures, unsigned long* hwcap) {
+ *hwcap |= has_list_item(cpuFeatures, "neon") ? NPY__HWCAP_NEON : 0;
+ *hwcap |= has_list_item(cpuFeatures, "half") ? NPY__HWCAP_HALF : 0;
+ *hwcap |= has_list_item(cpuFeatures, "vfpv3") ? NPY__HWCAP_VFPv3 : 0;
+ *hwcap |= has_list_item(cpuFeatures, "vfpv4") ? NPY__HWCAP_VFPv4 : 0;
+
+ *hwcap |= has_list_item(cpuFeatures, "asimd") ? NPY__HWCAP_ASIMD : 0;
+ *hwcap |= has_list_item(cpuFeatures, "fp") ? NPY__HWCAP_FP : 0;
+ *hwcap |= has_list_item(cpuFeatures, "fphp") ? NPY__HWCAP_FPHP : 0;
+ *hwcap |= has_list_item(cpuFeatures, "asimdhp") ? NPY__HWCAP_ASIMDHP : 0;
+ *hwcap |= has_list_item(cpuFeatures, "asimddp") ? NPY__HWCAP_ASIMDDP : 0;
+ *hwcap |= has_list_item(cpuFeatures, "asimdfhm") ? NPY__HWCAP_ASIMDFHM : 0;
+}
+
+static int
+get_feature_from_proc_cpuinfo(unsigned long *hwcap, unsigned long *hwcap2) {
+ char* cpuinfo = NULL;
+ int cpuinfo_len;
+ cpuinfo_len = get_file_size("/proc/cpuinfo");
+ if (cpuinfo_len < 0) {
+ return 0;
+ }
+ cpuinfo = malloc(cpuinfo_len);
+ if (cpuinfo == NULL) {
+ return 0;
+ }
+ cpuinfo_len = read_file("/proc/cpuinfo", cpuinfo, cpuinfo_len);
+ char* cpuFeatures = extract_cpuinfo_field(cpuinfo, cpuinfo_len, "Features");
+ if(cpuFeatures == NULL) {
+ return 0;
+ }
+ setHwcap(cpuFeatures, hwcap);
+ *hwcap2 |= *hwcap;
+ *hwcap2 |= has_list_item(cpuFeatures, "aes") ? NPY__HWCAP2_AES : 0;
+ *hwcap2 |= has_list_item(cpuFeatures, "pmull") ? NPY__HWCAP2_PMULL : 0;
+ *hwcap2 |= has_list_item(cpuFeatures, "sha1") ? NPY__HWCAP2_SHA1 : 0;
+ *hwcap2 |= has_list_item(cpuFeatures, "sha2") ? NPY__HWCAP2_SHA2 : 0;
+ *hwcap2 |= has_list_item(cpuFeatures, "crc32") ? NPY__HWCAP2_CRC32 : 0;
+ return 1;
+}
+#endif
diff --git a/numpy/core/src/common/simd/avx2/arithmetic.h b/numpy/core/src/common/simd/avx2/arithmetic.h new file mode 100644 index 000000000..9d8b4ab5e --- /dev/null +++ b/numpy/core/src/common/simd/avx2/arithmetic.h @@ -0,0 +1,75 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX2_ARITHMETIC_H +#define _NPY_SIMD_AVX2_ARITHMETIC_H + +/*************************** + * Addition + ***************************/ +// non-saturated +#define npyv_add_u8 _mm256_add_epi8 +#define npyv_add_s8 _mm256_add_epi8 +#define npyv_add_u16 _mm256_add_epi16 +#define npyv_add_s16 _mm256_add_epi16 +#define npyv_add_u32 _mm256_add_epi32 +#define npyv_add_s32 _mm256_add_epi32 +#define npyv_add_u64 _mm256_add_epi64 +#define npyv_add_s64 _mm256_add_epi64 +#define npyv_add_f32 _mm256_add_ps +#define npyv_add_f64 _mm256_add_pd + +// saturated +#define npyv_adds_u8 _mm256_adds_epu8 +#define npyv_adds_s8 _mm256_adds_epi8 +#define npyv_adds_u16 _mm256_adds_epu16 +#define npyv_adds_s16 _mm256_adds_epi16 +// TODO: rest, after implment Packs intrins + +/*************************** + * Subtraction + ***************************/ +// non-saturated +#define npyv_sub_u8 _mm256_sub_epi8 +#define npyv_sub_s8 _mm256_sub_epi8 +#define npyv_sub_u16 _mm256_sub_epi16 +#define npyv_sub_s16 _mm256_sub_epi16 +#define npyv_sub_u32 _mm256_sub_epi32 +#define npyv_sub_s32 _mm256_sub_epi32 +#define npyv_sub_u64 _mm256_sub_epi64 +#define npyv_sub_s64 _mm256_sub_epi64 +#define npyv_sub_f32 _mm256_sub_ps +#define npyv_sub_f64 _mm256_sub_pd + +// saturated +#define npyv_subs_u8 _mm256_subs_epu8 +#define npyv_subs_s8 _mm256_subs_epi8 +#define npyv_subs_u16 _mm256_subs_epu16 +#define npyv_subs_s16 _mm256_subs_epi16 +// TODO: rest, after implment Packs intrins + +/*************************** + * Multiplication + ***************************/ +// non-saturated +#define npyv_mul_u8 npyv256_mul_u8 +#define npyv_mul_s8 npyv_mul_u8 +#define npyv_mul_u16 _mm256_mullo_epi16 +#define npyv_mul_s16 _mm256_mullo_epi16 +#define npyv_mul_u32 _mm256_mullo_epi32 +#define npyv_mul_s32 _mm256_mullo_epi32 +#define npyv_mul_f32 _mm256_mul_ps +#define npyv_mul_f64 _mm256_mul_pd + +// saturated +// TODO: after implment Packs intrins + +/*************************** + * Division + ***************************/ +// TODO: emulate integer division +#define npyv_div_f32 _mm256_div_ps +#define npyv_div_f64 _mm256_div_pd + +#endif // _NPY_SIMD_AVX2_ARITHMETIC_H diff --git a/numpy/core/src/common/simd/avx2/avx2.h b/numpy/core/src/common/simd/avx2/avx2.h new file mode 100644 index 000000000..c99d628ee --- /dev/null +++ b/numpy/core/src/common/simd/avx2/avx2.h @@ -0,0 +1,67 @@ +#ifndef _NPY_SIMD_H_ + #error "Not a standalone header" +#endif + +#define NPY_SIMD 256 +#define NPY_SIMD_WIDTH 32 +#define NPY_SIMD_F64 1 + +typedef __m256i npyv_u8; +typedef __m256i npyv_s8; +typedef __m256i npyv_u16; +typedef __m256i npyv_s16; +typedef __m256i npyv_u32; +typedef __m256i npyv_s32; +typedef __m256i npyv_u64; +typedef __m256i npyv_s64; +typedef __m256 npyv_f32; +typedef __m256d npyv_f64; + +typedef __m256i npyv_b8; +typedef __m256i npyv_b16; +typedef __m256i npyv_b32; +typedef __m256i npyv_b64; + +typedef struct { __m256i val[2]; } npyv_m256ix2; +typedef npyv_m256ix2 npyv_u8x2; +typedef npyv_m256ix2 npyv_s8x2; +typedef npyv_m256ix2 npyv_u16x2; +typedef npyv_m256ix2 npyv_s16x2; +typedef npyv_m256ix2 npyv_u32x2; +typedef npyv_m256ix2 npyv_s32x2; +typedef npyv_m256ix2 npyv_u64x2; +typedef npyv_m256ix2 npyv_s64x2; + +typedef struct { __m256i val[3]; } npyv_m256ix3; +typedef npyv_m256ix3 npyv_u8x3; +typedef npyv_m256ix3 npyv_s8x3; +typedef npyv_m256ix3 npyv_u16x3; +typedef npyv_m256ix3 npyv_s16x3; +typedef npyv_m256ix3 npyv_u32x3; +typedef npyv_m256ix3 npyv_s32x3; +typedef npyv_m256ix3 npyv_u64x3; +typedef npyv_m256ix3 npyv_s64x3; + +typedef struct { __m256 val[2]; } npyv_f32x2; +typedef struct { __m256d val[2]; } npyv_f64x2; +typedef struct { __m256 val[3]; } npyv_f32x3; +typedef struct { __m256d val[3]; } npyv_f64x3; + +#define npyv_nlanes_u8 32 +#define npyv_nlanes_s8 32 +#define npyv_nlanes_u16 16 +#define npyv_nlanes_s16 16 +#define npyv_nlanes_u32 8 +#define npyv_nlanes_s32 8 +#define npyv_nlanes_u64 4 +#define npyv_nlanes_s64 4 +#define npyv_nlanes_f32 8 +#define npyv_nlanes_f64 4 + +#include "utils.h" +#include "memory.h" +#include "misc.h" +#include "reorder.h" +#include "operators.h" +#include "conversion.h" +#include "arithmetic.h" diff --git a/numpy/core/src/common/simd/avx2/conversion.h b/numpy/core/src/common/simd/avx2/conversion.h new file mode 100644 index 000000000..9fd86016d --- /dev/null +++ b/numpy/core/src/common/simd/avx2/conversion.h @@ -0,0 +1,32 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX2_CVT_H +#define _NPY_SIMD_AVX2_CVT_H + +// convert mask types to integer types +#define npyv_cvt_u8_b8(A) A +#define npyv_cvt_s8_b8(A) A +#define npyv_cvt_u16_b16(A) A +#define npyv_cvt_s16_b16(A) A +#define npyv_cvt_u32_b32(A) A +#define npyv_cvt_s32_b32(A) A +#define npyv_cvt_u64_b64(A) A +#define npyv_cvt_s64_b64(A) A +#define npyv_cvt_f32_b32(A) _mm256_castsi256_ps(A) +#define npyv_cvt_f64_b64(A) _mm256_castsi256_pd(A) + +// convert integer types to mask types +#define npyv_cvt_b8_u8(BL) BL +#define npyv_cvt_b8_s8(BL) BL +#define npyv_cvt_b16_u16(BL) BL +#define npyv_cvt_b16_s16(BL) BL +#define npyv_cvt_b32_u32(BL) BL +#define npyv_cvt_b32_s32(BL) BL +#define npyv_cvt_b64_u64(BL) BL +#define npyv_cvt_b64_s64(BL) BL +#define npyv_cvt_b32_f32(BL) _mm256_castps_si256(BL) +#define npyv_cvt_b64_f64(BL) _mm256_castpd_si256(BL) + +#endif // _NPY_SIMD_AVX2_CVT_H diff --git a/numpy/core/src/common/simd/avx2/memory.h b/numpy/core/src/common/simd/avx2/memory.h new file mode 100644 index 000000000..5ea7414fd --- /dev/null +++ b/numpy/core/src/common/simd/avx2/memory.h @@ -0,0 +1,70 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX2_MEMORY_H +#define _NPY_SIMD_AVX2_MEMORY_H + +/*************************** + * load/store + ***************************/ +#define NPYV_IMPL_AVX2_MEM_INT(CTYPE, SFX) \ + NPY_FINLINE npyv_##SFX npyv_load_##SFX(const CTYPE *ptr) \ + { return _mm256_loadu_si256((const __m256i*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loada_##SFX(const CTYPE *ptr) \ + { return _mm256_load_si256((const __m256i*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loads_##SFX(const CTYPE *ptr) \ + { return _mm256_stream_load_si256((const __m256i*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loadl_##SFX(const CTYPE *ptr) \ + { return _mm256_castsi128_si256(_mm_loadu_si128((const __m128i*)ptr)); } \ + NPY_FINLINE void npyv_store_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm256_storeu_si256((__m256i*)ptr, vec); } \ + NPY_FINLINE void npyv_storea_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm256_store_si256((__m256i*)ptr, vec); } \ + NPY_FINLINE void npyv_stores_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm256_stream_si256((__m256i*)ptr, vec); } \ + NPY_FINLINE void npyv_storel_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm_storeu_si128((__m128i*)(ptr), _mm256_castsi256_si128(vec)); } \ + NPY_FINLINE void npyv_storeh_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm_storeu_si128((__m128i*)(ptr), _mm256_extracti128_si256(vec, 1)); } + +NPYV_IMPL_AVX2_MEM_INT(npy_uint8, u8) +NPYV_IMPL_AVX2_MEM_INT(npy_int8, s8) +NPYV_IMPL_AVX2_MEM_INT(npy_uint16, u16) +NPYV_IMPL_AVX2_MEM_INT(npy_int16, s16) +NPYV_IMPL_AVX2_MEM_INT(npy_uint32, u32) +NPYV_IMPL_AVX2_MEM_INT(npy_int32, s32) +NPYV_IMPL_AVX2_MEM_INT(npy_uint64, u64) +NPYV_IMPL_AVX2_MEM_INT(npy_int64, s64) + +// unaligned load +#define npyv_load_f32 _mm256_loadu_ps +#define npyv_load_f64 _mm256_loadu_pd +// aligned load +#define npyv_loada_f32 _mm256_load_ps +#define npyv_loada_f64 _mm256_load_pd +// stream load +#define npyv_loads_f32(PTR) \ + _mm256_castsi256_ps(_mm256_stream_load_si256((const __m256i*)(PTR))) +#define npyv_loads_f64(PTR) \ + _mm256_castsi256_pd(_mm256_stream_load_si256((const __m256i*)(PTR))) +// load lower part +#define npyv_loadl_f32(PTR) _mm256_castps128_ps256(_mm_loadu_ps(PTR)) +#define npyv_loadl_f64(PTR) _mm256_castpd128_pd256(_mm_loadu_pd(PTR)) +// unaligned store +#define npyv_store_f32 _mm256_storeu_ps +#define npyv_store_f64 _mm256_storeu_pd +// aligned store +#define npyv_storea_f32 _mm256_store_ps +#define npyv_storea_f64 _mm256_store_pd +// stream store +#define npyv_stores_f32 _mm256_stream_ps +#define npyv_stores_f64 _mm256_stream_pd +// store lower part +#define npyv_storel_f32(PTR, VEC) _mm_storeu_ps(PTR, _mm256_castps256_ps128(VEC)) +#define npyv_storel_f64(PTR, VEC) _mm_storeu_pd(PTR, _mm256_castpd256_pd128(VEC)) +// store higher part +#define npyv_storeh_f32(PTR, VEC) _mm_storeu_ps(PTR, _mm256_extractf128_ps(VEC, 1)) +#define npyv_storeh_f64(PTR, VEC) _mm_storeu_pd(PTR, _mm256_extractf128_pd(VEC, 1)) + +#endif // _NPY_SIMD_AVX2_MEMORY_H diff --git a/numpy/core/src/common/simd/avx2/misc.h b/numpy/core/src/common/simd/avx2/misc.h new file mode 100644 index 000000000..e96696dc9 --- /dev/null +++ b/numpy/core/src/common/simd/avx2/misc.h @@ -0,0 +1,223 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX2_MISC_H +#define _NPY_SIMD_AVX2_MISC_H + +// vector with zero lanes +#define npyv_zero_u8 _mm256_setzero_si256 +#define npyv_zero_s8 _mm256_setzero_si256 +#define npyv_zero_u16 _mm256_setzero_si256 +#define npyv_zero_s16 _mm256_setzero_si256 +#define npyv_zero_u32 _mm256_setzero_si256 +#define npyv_zero_s32 _mm256_setzero_si256 +#define npyv_zero_u64 _mm256_setzero_si256 +#define npyv_zero_s64 _mm256_setzero_si256 +#define npyv_zero_f32 _mm256_setzero_ps +#define npyv_zero_f64 _mm256_setzero_pd + +// vector with a specific value set to all lanes +#define npyv_setall_u8(VAL) _mm256_set1_epi8((char)VAL) +#define npyv_setall_s8(VAL) _mm256_set1_epi8((char)VAL) +#define npyv_setall_u16(VAL) _mm256_set1_epi16((short)VAL) +#define npyv_setall_s16(VAL) _mm256_set1_epi16((short)VAL) +#define npyv_setall_u32(VAL) _mm256_set1_epi32((int)VAL) +#define npyv_setall_s32(VAL) _mm256_set1_epi32(VAL) +#define npyv_setall_u64(VAL) _mm256_set1_epi64x(VAL) +#define npyv_setall_s64(VAL) _mm256_set1_epi64x(VAL) +#define npyv_setall_f32(VAL) _mm256_set1_ps(VAL) +#define npyv_setall_f64(VAL) _mm256_set1_pd(VAL) + +/* + * vector with specific values set to each lane and + * set a specific value to all remained lanes + * + * Args that generated by NPYV__SET_FILL_* not going to expand if + * _mm256_setr_* are defined as macros. +*/ +NPY_FINLINE __m256i npyv__setr_epi8( + char i0, char i1, char i2, char i3, char i4, char i5, char i6, char i7, + char i8, char i9, char i10, char i11, char i12, char i13, char i14, char i15, + char i16, char i17, char i18, char i19, char i20, char i21, char i22, char i23, + char i24, char i25, char i26, char i27, char i28, char i29, char i30, char i31) +{ + return _mm256_setr_epi8( + i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15, + i16, i17, i18, i19, i20, i21, i22, i23, i24, i25, i26, i27, i28, i29, i30, i31 + ); +} +NPY_FINLINE __m256i npyv__setr_epi16( + short i0, short i1, short i2, short i3, short i4, short i5, short i6, short i7, + short i8, short i9, short i10, short i11, short i12, short i13, short i14, short i15) +{ + return _mm256_setr_epi16(i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15); +} +NPY_FINLINE __m256i npyv__setr_epi32(int i0, int i1, int i2, int i3, int i4, int i5, int i6, int i7) +{ + return _mm256_setr_epi32(i0, i1, i2, i3, i4, i5, i6, i7); +} +NPY_FINLINE __m256i npyv__setr_epi64(npy_int64 i0, npy_int64 i1, npy_int64 i2, npy_int64 i3) +{ + return _mm256_setr_epi64x(i0, i1, i2, i3); +} + +NPY_FINLINE __m256 npyv__setr_ps(float i0, float i1, float i2, float i3, float i4, float i5, + float i6, float i7) +{ + return _mm256_setr_ps(i0, i1, i2, i3, i4, i5, i6, i7); +} +NPY_FINLINE __m256d npyv__setr_pd(double i0, double i1, double i2, double i3) +{ + return _mm256_setr_pd(i0, i1, i2, i3); +} +#define npyv_setf_u8(FILL, ...) npyv__setr_epi8(NPYV__SET_FILL_32(char, FILL, __VA_ARGS__)) +#define npyv_setf_s8(FILL, ...) npyv__setr_epi8(NPYV__SET_FILL_32(char, FILL, __VA_ARGS__)) +#define npyv_setf_u16(FILL, ...) npyv__setr_epi16(NPYV__SET_FILL_16(short, FILL, __VA_ARGS__)) +#define npyv_setf_s16(FILL, ...) npyv__setr_epi16(NPYV__SET_FILL_16(short, FILL, __VA_ARGS__)) +#define npyv_setf_u32(FILL, ...) npyv__setr_epi32(NPYV__SET_FILL_8(int, FILL, __VA_ARGS__)) +#define npyv_setf_s32(FILL, ...) npyv__setr_epi32(NPYV__SET_FILL_8(int, FILL, __VA_ARGS__)) +#define npyv_setf_u64(FILL, ...) npyv__setr_epi64(NPYV__SET_FILL_4(npy_uint64, FILL, __VA_ARGS__)) +#define npyv_setf_s64(FILL, ...) npyv__setr_epi64(NPYV__SET_FILL_4(npy_int64, FILL, __VA_ARGS__)) +#define npyv_setf_f32(FILL, ...) npyv__setr_ps(NPYV__SET_FILL_8(float, FILL, __VA_ARGS__)) +#define npyv_setf_f64(FILL, ...) npyv__setr_pd(NPYV__SET_FILL_4(double, FILL, __VA_ARGS__)) + +// vector with specific values set to each lane and +// set zero to all remained lanes +#define npyv_set_u8(...) npyv_setf_u8(0, __VA_ARGS__) +#define npyv_set_s8(...) npyv_setf_s8(0, __VA_ARGS__) +#define npyv_set_u16(...) npyv_setf_u16(0, __VA_ARGS__) +#define npyv_set_s16(...) npyv_setf_s16(0, __VA_ARGS__) +#define npyv_set_u32(...) npyv_setf_u32(0, __VA_ARGS__) +#define npyv_set_s32(...) npyv_setf_s32(0, __VA_ARGS__) +#define npyv_set_u64(...) npyv_setf_u64(0, __VA_ARGS__) +#define npyv_set_s64(...) npyv_setf_s64(0, __VA_ARGS__) +#define npyv_set_f32(...) npyv_setf_f32(0, __VA_ARGS__) +#define npyv_set_f64(...) npyv_setf_f64(0, __VA_ARGS__) + +// Per lane select +#define npyv_select_u8(MASK, A, B) _mm256_blendv_epi8(B, A, MASK) +#define npyv_select_s8 npyv_select_u8 +#define npyv_select_u16 npyv_select_u8 +#define npyv_select_s16 npyv_select_u8 +#define npyv_select_u32 npyv_select_u8 +#define npyv_select_s32 npyv_select_u8 +#define npyv_select_u64 npyv_select_u8 +#define npyv_select_s64 npyv_select_u8 +#define npyv_select_f32(MASK, A, B) _mm256_blendv_ps(B, A, _mm256_castsi256_ps(MASK)) +#define npyv_select_f64(MASK, A, B) _mm256_blendv_pd(B, A, _mm256_castsi256_pd(MASK)) + +// Reinterpret +#define npyv_reinterpret_u8_u8(X) X +#define npyv_reinterpret_u8_s8(X) X +#define npyv_reinterpret_u8_u16(X) X +#define npyv_reinterpret_u8_s16(X) X +#define npyv_reinterpret_u8_u32(X) X +#define npyv_reinterpret_u8_s32(X) X +#define npyv_reinterpret_u8_u64(X) X +#define npyv_reinterpret_u8_s64(X) X +#define npyv_reinterpret_u8_f32 _mm256_castps_si256 +#define npyv_reinterpret_u8_f64 _mm256_castpd_si256 + +#define npyv_reinterpret_s8_s8(X) X +#define npyv_reinterpret_s8_u8(X) X +#define npyv_reinterpret_s8_u16(X) X +#define npyv_reinterpret_s8_s16(X) X +#define npyv_reinterpret_s8_u32(X) X +#define npyv_reinterpret_s8_s32(X) X +#define npyv_reinterpret_s8_u64(X) X +#define npyv_reinterpret_s8_s64(X) X +#define npyv_reinterpret_s8_f32 _mm256_castps_si256 +#define npyv_reinterpret_s8_f64 _mm256_castpd_si256 + +#define npyv_reinterpret_u16_u16(X) X +#define npyv_reinterpret_u16_u8(X) X +#define npyv_reinterpret_u16_s8(X) X +#define npyv_reinterpret_u16_s16(X) X +#define npyv_reinterpret_u16_u32(X) X +#define npyv_reinterpret_u16_s32(X) X +#define npyv_reinterpret_u16_u64(X) X +#define npyv_reinterpret_u16_s64(X) X +#define npyv_reinterpret_u16_f32 _mm256_castps_si256 +#define npyv_reinterpret_u16_f64 _mm256_castpd_si256 + +#define npyv_reinterpret_s16_s16(X) X +#define npyv_reinterpret_s16_u8(X) X +#define npyv_reinterpret_s16_s8(X) X +#define npyv_reinterpret_s16_u16(X) X +#define npyv_reinterpret_s16_u32(X) X +#define npyv_reinterpret_s16_s32(X) X +#define npyv_reinterpret_s16_u64(X) X +#define npyv_reinterpret_s16_s64(X) X +#define npyv_reinterpret_s16_f32 _mm256_castps_si256 +#define npyv_reinterpret_s16_f64 _mm256_castpd_si256 + +#define npyv_reinterpret_u32_u32(X) X +#define npyv_reinterpret_u32_u8(X) X +#define npyv_reinterpret_u32_s8(X) X +#define npyv_reinterpret_u32_u16(X) X +#define npyv_reinterpret_u32_s16(X) X +#define npyv_reinterpret_u32_s32(X) X +#define npyv_reinterpret_u32_u64(X) X +#define npyv_reinterpret_u32_s64(X) X +#define npyv_reinterpret_u32_f32 _mm256_castps_si256 +#define npyv_reinterpret_u32_f64 _mm256_castpd_si256 + +#define npyv_reinterpret_s32_s32(X) X +#define npyv_reinterpret_s32_u8(X) X +#define npyv_reinterpret_s32_s8(X) X +#define npyv_reinterpret_s32_u16(X) X +#define npyv_reinterpret_s32_s16(X) X +#define npyv_reinterpret_s32_u32(X) X +#define npyv_reinterpret_s32_u64(X) X +#define npyv_reinterpret_s32_s64(X) X +#define npyv_reinterpret_s32_f32 _mm256_castps_si256 +#define npyv_reinterpret_s32_f64 _mm256_castpd_si256 + +#define npyv_reinterpret_u64_u64(X) X +#define npyv_reinterpret_u64_u8(X) X +#define npyv_reinterpret_u64_s8(X) X +#define npyv_reinterpret_u64_u16(X) X +#define npyv_reinterpret_u64_s16(X) X +#define npyv_reinterpret_u64_u32(X) X +#define npyv_reinterpret_u64_s32(X) X +#define npyv_reinterpret_u64_s64(X) X +#define npyv_reinterpret_u64_f32 _mm256_castps_si256 +#define npyv_reinterpret_u64_f64 _mm256_castpd_si256 + +#define npyv_reinterpret_s64_s64(X) X +#define npyv_reinterpret_s64_u8(X) X +#define npyv_reinterpret_s64_s8(X) X +#define npyv_reinterpret_s64_u16(X) X +#define npyv_reinterpret_s64_s16(X) X +#define npyv_reinterpret_s64_u32(X) X +#define npyv_reinterpret_s64_s32(X) X +#define npyv_reinterpret_s64_u64(X) X +#define npyv_reinterpret_s64_f32 _mm256_castps_si256 +#define npyv_reinterpret_s64_f64 _mm256_castpd_si256 + +#define npyv_reinterpret_f32_f32(X) X +#define npyv_reinterpret_f32_u8 _mm256_castsi256_ps +#define npyv_reinterpret_f32_s8 _mm256_castsi256_ps +#define npyv_reinterpret_f32_u16 _mm256_castsi256_ps +#define npyv_reinterpret_f32_s16 _mm256_castsi256_ps +#define npyv_reinterpret_f32_u32 _mm256_castsi256_ps +#define npyv_reinterpret_f32_s32 _mm256_castsi256_ps +#define npyv_reinterpret_f32_u64 _mm256_castsi256_ps +#define npyv_reinterpret_f32_s64 _mm256_castsi256_ps +#define npyv_reinterpret_f32_f64 _mm256_castpd_ps + +#define npyv_reinterpret_f64_f64(X) X +#define npyv_reinterpret_f64_u8 _mm256_castsi256_pd +#define npyv_reinterpret_f64_s8 _mm256_castsi256_pd +#define npyv_reinterpret_f64_u16 _mm256_castsi256_pd +#define npyv_reinterpret_f64_s16 _mm256_castsi256_pd +#define npyv_reinterpret_f64_u32 _mm256_castsi256_pd +#define npyv_reinterpret_f64_s32 _mm256_castsi256_pd +#define npyv_reinterpret_f64_u64 _mm256_castsi256_pd +#define npyv_reinterpret_f64_s64 _mm256_castsi256_pd +#define npyv_reinterpret_f64_f32 _mm256_castps_pd + +#define npyv_cleanup _mm256_zeroall + +#endif // _NPY_SIMD_SSE_MISC_H diff --git a/numpy/core/src/common/simd/avx2/operators.h b/numpy/core/src/common/simd/avx2/operators.h new file mode 100644 index 000000000..c1d30413f --- /dev/null +++ b/numpy/core/src/common/simd/avx2/operators.h @@ -0,0 +1,200 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX2_OPERATORS_H +#define _NPY_SIMD_AVX2_OPERATORS_H + +/*************************** + * Shifting + ***************************/ + +// left +#define npyv_shl_u16(A, C) _mm256_sll_epi16(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_s16(A, C) _mm256_sll_epi16(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_u32(A, C) _mm256_sll_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_s32(A, C) _mm256_sll_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_u64(A, C) _mm256_sll_epi64(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_s64(A, C) _mm256_sll_epi64(A, _mm_cvtsi32_si128(C)) + +// left by an immediate constant +#define npyv_shli_u16 _mm256_slli_epi16 +#define npyv_shli_s16 _mm256_slli_epi16 +#define npyv_shli_u32 _mm256_slli_epi32 +#define npyv_shli_s32 _mm256_slli_epi32 +#define npyv_shli_u64 _mm256_slli_epi64 +#define npyv_shli_s64 _mm256_slli_epi64 + +// right +#define npyv_shr_u16(A, C) _mm256_srl_epi16(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_s16(A, C) _mm256_sra_epi16(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_u32(A, C) _mm256_srl_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_s32(A, C) _mm256_sra_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_u64(A, C) _mm256_srl_epi64(A, _mm_cvtsi32_si128(C)) +NPY_FINLINE __m256i npyv_shr_s64(__m256i a, int c) +{ + const __m256i sbit = _mm256_set1_epi64x(0x8000000000000000); + const __m128i c64 = _mm_cvtsi32_si128(c); + __m256i r = _mm256_srl_epi64(_mm256_add_epi64(a, sbit), c64); + return _mm256_sub_epi64(r, _mm256_srl_epi64(sbit, c64)); +} + +// right by an immediate constant +#define npyv_shri_u16 _mm256_srli_epi16 +#define npyv_shri_s16 _mm256_srai_epi16 +#define npyv_shri_u32 _mm256_srli_epi32 +#define npyv_shri_s32 _mm256_srai_epi32 +#define npyv_shri_u64 _mm256_srli_epi64 +#define npyv_shri_s64 npyv_shr_s64 + +/*************************** + * Logical + ***************************/ +// AND +#define npyv_and_u8 _mm256_and_si256 +#define npyv_and_s8 _mm256_and_si256 +#define npyv_and_u16 _mm256_and_si256 +#define npyv_and_s16 _mm256_and_si256 +#define npyv_and_u32 _mm256_and_si256 +#define npyv_and_s32 _mm256_and_si256 +#define npyv_and_u64 _mm256_and_si256 +#define npyv_and_s64 _mm256_and_si256 +#define npyv_and_f32 _mm256_and_ps +#define npyv_and_f64 _mm256_and_pd + +// OR +#define npyv_or_u8 _mm256_or_si256 +#define npyv_or_s8 _mm256_or_si256 +#define npyv_or_u16 _mm256_or_si256 +#define npyv_or_s16 _mm256_or_si256 +#define npyv_or_u32 _mm256_or_si256 +#define npyv_or_s32 _mm256_or_si256 +#define npyv_or_u64 _mm256_or_si256 +#define npyv_or_s64 _mm256_or_si256 +#define npyv_or_f32 _mm256_or_ps +#define npyv_or_f64 _mm256_or_pd + +// XOR +#define npyv_xor_u8 _mm256_xor_si256 +#define npyv_xor_s8 _mm256_xor_si256 +#define npyv_xor_u16 _mm256_xor_si256 +#define npyv_xor_s16 _mm256_xor_si256 +#define npyv_xor_u32 _mm256_xor_si256 +#define npyv_xor_s32 _mm256_xor_si256 +#define npyv_xor_u64 _mm256_xor_si256 +#define npyv_xor_s64 _mm256_xor_si256 +#define npyv_xor_f32 _mm256_xor_ps +#define npyv_xor_f64 _mm256_xor_pd + +// NOT +#define npyv_not_u8(A) _mm256_xor_si256(A, _mm256_set1_epi32(-1)) +#define npyv_not_s8 npyv_not_u8 +#define npyv_not_u16 npyv_not_u8 +#define npyv_not_s16 npyv_not_u8 +#define npyv_not_u32 npyv_not_u8 +#define npyv_not_s32 npyv_not_u8 +#define npyv_not_u64 npyv_not_u8 +#define npyv_not_s64 npyv_not_u8 +#define npyv_not_f32(A) _mm256_xor_ps(A, _mm256_castsi256_ps(_mm256_set1_epi32(-1))) +#define npyv_not_f64(A) _mm256_xor_pd(A, _mm256_castsi256_pd(_mm256_set1_epi32(-1))) + +/*************************** + * Comparison + ***************************/ + +// int Equal +#define npyv_cmpeq_u8 _mm256_cmpeq_epi8 +#define npyv_cmpeq_s8 _mm256_cmpeq_epi8 +#define npyv_cmpeq_u16 _mm256_cmpeq_epi16 +#define npyv_cmpeq_s16 _mm256_cmpeq_epi16 +#define npyv_cmpeq_u32 _mm256_cmpeq_epi32 +#define npyv_cmpeq_s32 _mm256_cmpeq_epi32 +#define npyv_cmpeq_u64 _mm256_cmpeq_epi64 +#define npyv_cmpeq_s64 _mm256_cmpeq_epi64 + +// int Not Equal +#define npyv_cmpneq_u8(A, B) npyv_not_u8(_mm256_cmpeq_epi8(A, B)) +#define npyv_cmpneq_s8 npyv_cmpneq_u8 +#define npyv_cmpneq_u16(A, B) npyv_not_u16(_mm256_cmpeq_epi16(A, B)) +#define npyv_cmpneq_s16 npyv_cmpneq_u16 +#define npyv_cmpneq_u32(A, B) npyv_not_u32(_mm256_cmpeq_epi32(A, B)) +#define npyv_cmpneq_s32 npyv_cmpneq_u32 +#define npyv_cmpneq_u64(A, B) npyv_not_u64(_mm256_cmpeq_epi64(A, B)) +#define npyv_cmpneq_s64 npyv_cmpneq_u64 + +// signed greater than +#define npyv_cmpgt_s8 _mm256_cmpgt_epi8 +#define npyv_cmpgt_s16 _mm256_cmpgt_epi16 +#define npyv_cmpgt_s32 _mm256_cmpgt_epi32 +#define npyv_cmpgt_s64 _mm256_cmpgt_epi64 + +// signed greater than or equal +#define npyv_cmpge_s8(A, B) npyv_not_s8(_mm256_cmpgt_epi8(B, A)) +#define npyv_cmpge_s16(A, B) npyv_not_s16(_mm256_cmpgt_epi16(B, A)) +#define npyv_cmpge_s32(A, B) npyv_not_s32(_mm256_cmpgt_epi32(B, A)) +#define npyv_cmpge_s64(A, B) npyv_not_s64(_mm256_cmpgt_epi64(B, A)) + +// unsigned greater than +#define NPYV_IMPL_AVX2_UNSIGNED_GT(LEN, SIGN) \ + NPY_FINLINE __m256i npyv_cmpgt_u##LEN(__m256i a, __m256i b) \ + { \ + const __m256i sbit = _mm256_set1_epi32(SIGN); \ + return _mm256_cmpgt_epi##LEN( \ + _mm256_xor_si256(a, sbit), _mm256_xor_si256(b, sbit) \ + ); \ + } + +NPYV_IMPL_AVX2_UNSIGNED_GT(8, 0x80808080) +NPYV_IMPL_AVX2_UNSIGNED_GT(16, 0x80008000) +NPYV_IMPL_AVX2_UNSIGNED_GT(32, 0x80000000) + +NPY_FINLINE __m256i npyv_cmpgt_u64(__m256i a, __m256i b) +{ + const __m256i sbit = _mm256_set1_epi64x(0x8000000000000000); + return _mm256_cmpgt_epi64(_mm256_xor_si256(a, sbit), _mm256_xor_si256(b, sbit)); +} + +// unsigned greater than or equal +NPY_FINLINE __m256i npyv_cmpge_u8(__m256i a, __m256i b) +{ return _mm256_cmpeq_epi8(a, _mm256_max_epu8(a, b)); } +NPY_FINLINE __m256i npyv_cmpge_u16(__m256i a, __m256i b) +{ return _mm256_cmpeq_epi16(a, _mm256_max_epu16(a, b)); } +NPY_FINLINE __m256i npyv_cmpge_u32(__m256i a, __m256i b) +{ return _mm256_cmpeq_epi32(a, _mm256_max_epu32(a, b)); } +#define npyv_cmpge_u64(A, B) npyv_not_u64(npyv_cmpgt_u64(B, A)) + +// less than +#define npyv_cmplt_u8(A, B) npyv_cmpgt_u8(B, A) +#define npyv_cmplt_s8(A, B) npyv_cmpgt_s8(B, A) +#define npyv_cmplt_u16(A, B) npyv_cmpgt_u16(B, A) +#define npyv_cmplt_s16(A, B) npyv_cmpgt_s16(B, A) +#define npyv_cmplt_u32(A, B) npyv_cmpgt_u32(B, A) +#define npyv_cmplt_s32(A, B) npyv_cmpgt_s32(B, A) +#define npyv_cmplt_u64(A, B) npyv_cmpgt_u64(B, A) +#define npyv_cmplt_s64(A, B) npyv_cmpgt_s64(B, A) + +// less than or equal +#define npyv_cmple_u8(A, B) npyv_cmpge_u8(B, A) +#define npyv_cmple_s8(A, B) npyv_cmpge_s8(B, A) +#define npyv_cmple_u16(A, B) npyv_cmpge_u16(B, A) +#define npyv_cmple_s16(A, B) npyv_cmpge_s16(B, A) +#define npyv_cmple_u32(A, B) npyv_cmpge_u32(B, A) +#define npyv_cmple_s32(A, B) npyv_cmpge_s32(B, A) +#define npyv_cmple_u64(A, B) npyv_cmpge_u64(B, A) +#define npyv_cmple_s64(A, B) npyv_cmpge_s64(B, A) + +// precision comparison +#define npyv_cmpeq_f32(A, B) _mm256_castps_si256(_mm256_cmp_ps(A, B, _CMP_EQ_OQ)) +#define npyv_cmpeq_f64(A, B) _mm256_castpd_si256(_mm256_cmp_pd(A, B, _CMP_EQ_OQ)) +#define npyv_cmpneq_f32(A, B) _mm256_castps_si256(_mm256_cmp_ps(A, B, _CMP_NEQ_OQ)) +#define npyv_cmpneq_f64(A, B) _mm256_castpd_si256(_mm256_cmp_pd(A, B, _CMP_NEQ_OQ)) +#define npyv_cmplt_f32(A, B) _mm256_castps_si256(_mm256_cmp_ps(A, B, _CMP_LT_OQ)) +#define npyv_cmplt_f64(A, B) _mm256_castpd_si256(_mm256_cmp_pd(A, B, _CMP_LT_OQ)) +#define npyv_cmple_f32(A, B) _mm256_castps_si256(_mm256_cmp_ps(A, B, _CMP_LE_OQ)) +#define npyv_cmple_f64(A, B) _mm256_castpd_si256(_mm256_cmp_pd(A, B, _CMP_LE_OQ)) +#define npyv_cmpgt_f32(A, B) _mm256_castps_si256(_mm256_cmp_ps(A, B, _CMP_GT_OQ)) +#define npyv_cmpgt_f64(A, B) _mm256_castpd_si256(_mm256_cmp_pd(A, B, _CMP_GT_OQ)) +#define npyv_cmpge_f32(A, B) _mm256_castps_si256(_mm256_cmp_ps(A, B, _CMP_GE_OQ)) +#define npyv_cmpge_f64(A, B) _mm256_castpd_si256(_mm256_cmp_pd(A, B, _CMP_GE_OQ)) + +#endif // _NPY_SIMD_AVX2_OPERATORS_H diff --git a/numpy/core/src/common/simd/avx2/reorder.h b/numpy/core/src/common/simd/avx2/reorder.h new file mode 100644 index 000000000..5a9e68e32 --- /dev/null +++ b/numpy/core/src/common/simd/avx2/reorder.h @@ -0,0 +1,97 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX2_REORDER_H +#define _NPY_SIMD_AVX2_REORDER_H + +// combine lower part of two vectors +#define npyv_combinel_u8(A, B) _mm256_permute2x128_si256(A, B, 0x20) +#define npyv_combinel_s8 npyv_combinel_u8 +#define npyv_combinel_u16 npyv_combinel_u8 +#define npyv_combinel_s16 npyv_combinel_u8 +#define npyv_combinel_u32 npyv_combinel_u8 +#define npyv_combinel_s32 npyv_combinel_u8 +#define npyv_combinel_u64 npyv_combinel_u8 +#define npyv_combinel_s64 npyv_combinel_u8 +#define npyv_combinel_f32(A, B) _mm256_permute2f128_ps(A, B, 0x20) +#define npyv_combinel_f64(A, B) _mm256_permute2f128_pd(A, B, 0x20) + +// combine higher part of two vectors +#define npyv_combineh_u8(A, B) _mm256_permute2x128_si256(A, B, 0x31) +#define npyv_combineh_s8 npyv_combineh_u8 +#define npyv_combineh_u16 npyv_combineh_u8 +#define npyv_combineh_s16 npyv_combineh_u8 +#define npyv_combineh_u32 npyv_combineh_u8 +#define npyv_combineh_s32 npyv_combineh_u8 +#define npyv_combineh_u64 npyv_combineh_u8 +#define npyv_combineh_s64 npyv_combineh_u8 +#define npyv_combineh_f32(A, B) _mm256_permute2f128_ps(A, B, 0x31) +#define npyv_combineh_f64(A, B) _mm256_permute2f128_pd(A, B, 0x31) + +// combine two vectors from lower and higher parts of two other vectors +NPY_FINLINE npyv_m256ix2 npyv__combine(__m256i a, __m256i b) +{ + npyv_m256ix2 r; + __m256i a1b0 = _mm256_permute2x128_si256(a, b, 0x21); + r.val[0] = _mm256_blend_epi32(a, a1b0, 0xF0); + r.val[1] = _mm256_blend_epi32(b, a1b0, 0xF); + return r; +} +NPY_FINLINE npyv_f32x2 npyv_combine_f32(__m256 a, __m256 b) +{ + npyv_f32x2 r; + __m256 a1b0 = _mm256_permute2f128_ps(a, b, 0x21); + r.val[0] = _mm256_blend_ps(a, a1b0, 0xF0); + r.val[1] = _mm256_blend_ps(b, a1b0, 0xF); + return r; +} +NPY_FINLINE npyv_f64x2 npyv_combine_f64(__m256d a, __m256d b) +{ + npyv_f64x2 r; + __m256d a1b0 = _mm256_permute2f128_pd(a, b, 0x21); + r.val[0] = _mm256_blend_pd(a, a1b0, 0xC); + r.val[1] = _mm256_blend_pd(b, a1b0, 0x3); + return r; +} +#define npyv_combine_u8 npyv__combine +#define npyv_combine_s8 npyv__combine +#define npyv_combine_u16 npyv__combine +#define npyv_combine_s16 npyv__combine +#define npyv_combine_u32 npyv__combine +#define npyv_combine_s32 npyv__combine +#define npyv_combine_u64 npyv__combine +#define npyv_combine_s64 npyv__combine + +// interleave two vectors +#define NPYV_IMPL_AVX2_ZIP_U(T_VEC, LEN) \ + NPY_FINLINE T_VEC##x2 npyv_zip_u##LEN(T_VEC a, T_VEC b) \ + { \ + __m256i ab0 = _mm256_unpacklo_epi##LEN(a, b); \ + __m256i ab1 = _mm256_unpackhi_epi##LEN(a, b); \ + return npyv__combine(ab0, ab1); \ + } + +NPYV_IMPL_AVX2_ZIP_U(npyv_u8, 8) +NPYV_IMPL_AVX2_ZIP_U(npyv_u16, 16) +NPYV_IMPL_AVX2_ZIP_U(npyv_u32, 32) +NPYV_IMPL_AVX2_ZIP_U(npyv_u64, 64) +#define npyv_zip_s8 npyv_zip_u8 +#define npyv_zip_s16 npyv_zip_u16 +#define npyv_zip_s32 npyv_zip_u32 +#define npyv_zip_s64 npyv_zip_u64 + +NPY_FINLINE npyv_f32x2 npyv_zip_f32(__m256 a, __m256 b) +{ + __m256 ab0 = _mm256_unpacklo_ps(a, b); + __m256 ab1 = _mm256_unpackhi_ps(a, b); + return npyv_combine_f32(ab0, ab1); +} +NPY_FINLINE npyv_f64x2 npyv_zip_f64(__m256d a, __m256d b) +{ + __m256d ab0 = _mm256_unpacklo_pd(a, b); + __m256d ab1 = _mm256_unpackhi_pd(a, b); + return npyv_combine_f64(ab0, ab1); +} + +#endif // _NPY_SIMD_AVX2_REORDER_H diff --git a/numpy/core/src/common/simd/avx2/utils.h b/numpy/core/src/common/simd/avx2/utils.h new file mode 100644 index 000000000..24f1af5d1 --- /dev/null +++ b/numpy/core/src/common/simd/avx2/utils.h @@ -0,0 +1,21 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX2_UTILS_H +#define _NPY_SIMD_AVX2_UTILS_H + +#define npyv256_shuffle_odd(A) _mm256_permute4x64_epi64(A, _MM_SHUFFLE(3, 1, 2, 0)) +#define npyv256_shuffle_odd_ps(A) _mm256_castsi256_ps(npyv256_shuffle_odd(_mm256_castps_si256(A))) +#define npyv256_shuffle_odd_pd(A) _mm256_permute4x64_pd(A, _MM_SHUFFLE(3, 1, 2, 0)) + +NPY_FINLINE __m256i npyv256_mul_u8(__m256i a, __m256i b) +{ + const __m256i mask = _mm256_set1_epi32(0xFF00FF00); + __m256i even = _mm256_mullo_epi16(a, b); + __m256i odd = _mm256_mullo_epi16(_mm256_srai_epi16(a, 8), _mm256_srai_epi16(b, 8)); + odd = _mm256_slli_epi16(odd, 8); + return _mm256_blendv_epi8(even, odd, mask); +} + +#endif // _NPY_SIMD_AVX2_UTILS_H diff --git a/numpy/core/src/common/simd/avx512/arithmetic.h b/numpy/core/src/common/simd/avx512/arithmetic.h new file mode 100644 index 000000000..fcaef0efd --- /dev/null +++ b/numpy/core/src/common/simd/avx512/arithmetic.h @@ -0,0 +1,116 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX512_ARITHMETIC_H +#define _NPY_SIMD_AVX512_ARITHMETIC_H + +#include "../avx2/utils.h" + +/*************************** + * Addition + ***************************/ +// non-saturated +#ifdef NPY_HAVE_AVX512BW + #define npyv_add_u8 _mm512_add_epi8 + #define npyv_add_u16 _mm512_add_epi16 +#else + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_add_u8, _mm256_add_epi8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_add_u16, _mm256_add_epi16) +#endif +#define npyv_add_s8 npyv_add_u8 +#define npyv_add_s16 npyv_add_u16 +#define npyv_add_u32 _mm512_add_epi32 +#define npyv_add_s32 _mm512_add_epi32 +#define npyv_add_u64 _mm512_add_epi64 +#define npyv_add_s64 _mm512_add_epi64 +#define npyv_add_f32 _mm512_add_ps +#define npyv_add_f64 _mm512_add_pd + +// saturated +#ifdef NPY_HAVE_AVX512BW + #define npyv_adds_u8 _mm512_adds_epu8 + #define npyv_adds_s8 _mm512_adds_epi8 + #define npyv_adds_u16 _mm512_adds_epu16 + #define npyv_adds_s16 _mm512_adds_epi16 +#else + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_adds_u8, _mm256_adds_epu8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_adds_s8, _mm256_adds_epi8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_adds_u16, _mm256_adds_epu16) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_adds_s16, _mm256_adds_epi16) +#endif +// TODO: rest, after implment Packs intrins + +/*************************** + * Subtraction + ***************************/ +// non-saturated +#ifdef NPY_HAVE_AVX512BW + #define npyv_sub_u8 _mm512_sub_epi8 + #define npyv_sub_u16 _mm512_sub_epi16 +#else + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_sub_u8, _mm256_sub_epi8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_sub_u16, _mm256_sub_epi16) +#endif +#define npyv_sub_s8 npyv_sub_u8 +#define npyv_sub_s16 npyv_sub_u16 +#define npyv_sub_u32 _mm512_sub_epi32 +#define npyv_sub_s32 _mm512_sub_epi32 +#define npyv_sub_u64 _mm512_sub_epi64 +#define npyv_sub_s64 _mm512_sub_epi64 +#define npyv_sub_f32 _mm512_sub_ps +#define npyv_sub_f64 _mm512_sub_pd + +// saturated +#ifdef NPY_HAVE_AVX512BW + #define npyv_subs_u8 _mm512_subs_epu8 + #define npyv_subs_s8 _mm512_subs_epi8 + #define npyv_subs_u16 _mm512_subs_epu16 + #define npyv_subs_s16 _mm512_subs_epi16 +#else + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_subs_u8, _mm256_subs_epu8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_subs_s8, _mm256_subs_epi8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_subs_u16, _mm256_subs_epu16) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_subs_s16, _mm256_subs_epi16) +#endif +// TODO: rest, after implment Packs intrins + +/*************************** + * Multiplication + ***************************/ +// non-saturated +#ifdef NPY_HAVE_AVX512BW +NPY_FINLINE __m512i npyv_mul_u8(__m512i a, __m512i b) +{ + __m512i even = _mm512_mullo_epi16(a, b); + __m512i odd = _mm512_mullo_epi16(_mm512_srai_epi16(a, 8), _mm512_srai_epi16(b, 8)); + odd = _mm512_slli_epi16(odd, 8); + return _mm512_mask_blend_epi8(0xAAAAAAAAAAAAAAAA, even, odd); +} +#else + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_mul_u8, npyv256_mul_u8) +#endif + +#ifdef NPY_HAVE_AVX512BW + #define npyv_mul_u16 _mm512_mullo_epi16 +#else + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_mul_u16, _mm256_mullo_epi16) +#endif +#define npyv_mul_s8 npyv_mul_u8 +#define npyv_mul_s16 npyv_mul_u16 +#define npyv_mul_u32 _mm512_mullo_epi32 +#define npyv_mul_s32 _mm512_mullo_epi32 +#define npyv_mul_f32 _mm512_mul_ps +#define npyv_mul_f64 _mm512_mul_pd + +// saturated +// TODO: after implment Packs intrins + +/*************************** + * Division + ***************************/ +// TODO: emulate integer division +#define npyv_div_f32 _mm512_div_ps +#define npyv_div_f64 _mm512_div_pd + +#endif // _NPY_SIMD_AVX512_ARITHMETIC_H diff --git a/numpy/core/src/common/simd/avx512/avx512.h b/numpy/core/src/common/simd/avx512/avx512.h new file mode 100644 index 000000000..96fdf72b9 --- /dev/null +++ b/numpy/core/src/common/simd/avx512/avx512.h @@ -0,0 +1,71 @@ +#ifndef _NPY_SIMD_H_ + #error "Not a standalone header" +#endif +#define NPY_SIMD 512 +#define NPY_SIMD_WIDTH 64 +#define NPY_SIMD_F64 1 + +typedef __m512i npyv_u8; +typedef __m512i npyv_s8; +typedef __m512i npyv_u16; +typedef __m512i npyv_s16; +typedef __m512i npyv_u32; +typedef __m512i npyv_s32; +typedef __m512i npyv_u64; +typedef __m512i npyv_s64; +typedef __m512 npyv_f32; +typedef __m512d npyv_f64; + +#ifdef NPY_HAVE_AVX512BW +typedef __mmask64 npyv_b8; +typedef __mmask32 npyv_b16; +#else +typedef __m512i npyv_b8; +typedef __m512i npyv_b16; +#endif +typedef __mmask16 npyv_b32; +typedef __mmask8 npyv_b64; + +typedef struct { __m512i val[2]; } npyv_m512ix2; +typedef npyv_m512ix2 npyv_u8x2; +typedef npyv_m512ix2 npyv_s8x2; +typedef npyv_m512ix2 npyv_u16x2; +typedef npyv_m512ix2 npyv_s16x2; +typedef npyv_m512ix2 npyv_u32x2; +typedef npyv_m512ix2 npyv_s32x2; +typedef npyv_m512ix2 npyv_u64x2; +typedef npyv_m512ix2 npyv_s64x2; + +typedef struct { __m512i val[3]; } npyv_m512ix3; +typedef npyv_m512ix3 npyv_u8x3; +typedef npyv_m512ix3 npyv_s8x3; +typedef npyv_m512ix3 npyv_u16x3; +typedef npyv_m512ix3 npyv_s16x3; +typedef npyv_m512ix3 npyv_u32x3; +typedef npyv_m512ix3 npyv_s32x3; +typedef npyv_m512ix3 npyv_u64x3; +typedef npyv_m512ix3 npyv_s64x3; + +typedef struct { __m512 val[2]; } npyv_f32x2; +typedef struct { __m512d val[2]; } npyv_f64x2; +typedef struct { __m512 val[3]; } npyv_f32x3; +typedef struct { __m512d val[3]; } npyv_f64x3; + +#define npyv_nlanes_u8 64 +#define npyv_nlanes_s8 64 +#define npyv_nlanes_u16 32 +#define npyv_nlanes_s16 32 +#define npyv_nlanes_u32 16 +#define npyv_nlanes_s32 16 +#define npyv_nlanes_u64 8 +#define npyv_nlanes_s64 8 +#define npyv_nlanes_f32 16 +#define npyv_nlanes_f64 8 + +#include "utils.h" +#include "memory.h" +#include "misc.h" +#include "reorder.h" +#include "operators.h" +#include "conversion.h" +#include "arithmetic.h" diff --git a/numpy/core/src/common/simd/avx512/conversion.h b/numpy/core/src/common/simd/avx512/conversion.h new file mode 100644 index 000000000..0f7e27de3 --- /dev/null +++ b/numpy/core/src/common/simd/avx512/conversion.h @@ -0,0 +1,54 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX512_CVT_H +#define _NPY_SIMD_AVX512_CVT_H + +// convert mask to integer vectors +#ifdef NPY_HAVE_AVX512BW + #define npyv_cvt_u8_b8 _mm512_movm_epi8 + #define npyv_cvt_u16_b16 _mm512_movm_epi16 +#else + #define npyv_cvt_u8_b8(BL) BL + #define npyv_cvt_u16_b16(BL) BL +#endif +#define npyv_cvt_s8_b8 npyv_cvt_u8_b8 +#define npyv_cvt_s16_b16 npyv_cvt_u16_b16 + +#ifdef NPY_HAVE_AVX512DQ + #define npyv_cvt_u32_b32 _mm512_movm_epi32 + #define npyv_cvt_u64_b64 _mm512_movm_epi64 +#else + #define npyv_cvt_u32_b32(BL) _mm512_maskz_set1_epi32(BL, (int)-1) + #define npyv_cvt_u64_b64(BL) _mm512_maskz_set1_epi64(BL, (npy_int64)-1) +#endif +#define npyv_cvt_s32_b32 npyv_cvt_u32_b32 +#define npyv_cvt_s64_b64 npyv_cvt_u64_b64 +#define npyv_cvt_f32_b32(BL) _mm512_castsi512_ps(npyv_cvt_u32_b32(BL)) +#define npyv_cvt_f64_b64(BL) _mm512_castsi512_pd(npyv_cvt_u64_b64(BL)) + +// convert integer vectors to mask +#ifdef NPY_HAVE_AVX512BW + #define npyv_cvt_b8_u8 _mm512_movepi8_mask + #define npyv_cvt_b16_u16 _mm512_movepi16_mask +#else + #define npyv_cvt_b8_u8(A) A + #define npyv_cvt_b16_u16(A) A +#endif +#define npyv_cvt_b8_s8 npyv_cvt_b8_u8 +#define npyv_cvt_b16_s16 npyv_cvt_b16_u16 + +#ifdef NPY_HAVE_AVX512DQ + #define npyv_cvt_b32_u32 _mm512_movepi32_mask + #define npyv_cvt_b64_u64 _mm512_movepi64_mask +#else + #define npyv_cvt_b32_u32(A) _mm512_cmpneq_epu32_mask(A, _mm512_setzero_si512()) + #define npyv_cvt_b64_u64(A) _mm512_cmpneq_epu64_mask(A, _mm512_setzero_si512()) +#endif +#define npyv_cvt_b32_s32 npyv_cvt_b32_u32 +#define npyv_cvt_b64_s64 npyv_cvt_b64_u64 +#define npyv_cvt_b32_f32(A) npyv_cvt_b32_u32(_mm512_castps_si512(A)) +#define npyv_cvt_b64_f64(A) npyv_cvt_b64_u64(_mm512_castpd_si512(A)) + +#endif // _NPY_SIMD_AVX512_CVT_H diff --git a/numpy/core/src/common/simd/avx512/memory.h b/numpy/core/src/common/simd/avx512/memory.h new file mode 100644 index 000000000..e212c4555 --- /dev/null +++ b/numpy/core/src/common/simd/avx512/memory.h @@ -0,0 +1,94 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX512_MEMORY_H +#define _NPY_SIMD_AVX512_MEMORY_H + +#include "misc.h" + +/*************************** + * load/store + ***************************/ +#if defined(__GNUC__) + // GCC expect pointer argument type to be `void*` instead of `const void *`, + // which cause a massive warning. + #define npyv__loads(PTR) _mm512_stream_load_si512((__m512i*)(PTR)) +#else + #define npyv__loads(PTR) _mm512_stream_load_si512((const __m512i*)(PTR)) +#endif +#if defined(_MSC_VER) && defined(_M_IX86) + // workaround msvc(32bit) overflow bug, reported at + // https://developercommunity.visualstudio.com/content/problem/911872/u.html + NPY_FINLINE __m512i npyv__loadl(const __m256i *ptr) + { + __m256i a = _mm256_loadu_si256(ptr); + return _mm512_inserti64x4(_mm512_castsi256_si512(a), a, 0); + } +#else + #define npyv__loadl(PTR) \ + _mm512_castsi256_si512(_mm256_loadu_si256(PTR)) +#endif +#define NPYV_IMPL_AVX512_MEM_INT(CTYPE, SFX) \ + NPY_FINLINE npyv_##SFX npyv_load_##SFX(const CTYPE *ptr) \ + { return _mm512_loadu_si512((const __m512i*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loada_##SFX(const CTYPE *ptr) \ + { return _mm512_load_si512((const __m512i*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loads_##SFX(const CTYPE *ptr) \ + { return npyv__loads(ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loadl_##SFX(const CTYPE *ptr) \ + { return npyv__loadl((const __m256i *)ptr); } \ + NPY_FINLINE void npyv_store_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm512_storeu_si512((__m512i*)ptr, vec); } \ + NPY_FINLINE void npyv_storea_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm512_store_si512((__m512i*)ptr, vec); } \ + NPY_FINLINE void npyv_stores_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm512_stream_si512((__m512i*)ptr, vec); } \ + NPY_FINLINE void npyv_storel_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm256_storeu_si256((__m256i*)ptr, npyv512_lower_si256(vec)); } \ + NPY_FINLINE void npyv_storeh_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm256_storeu_si256((__m256i*)(ptr), npyv512_higher_si256(vec)); } + +NPYV_IMPL_AVX512_MEM_INT(npy_uint8, u8) +NPYV_IMPL_AVX512_MEM_INT(npy_int8, s8) +NPYV_IMPL_AVX512_MEM_INT(npy_uint16, u16) +NPYV_IMPL_AVX512_MEM_INT(npy_int16, s16) +NPYV_IMPL_AVX512_MEM_INT(npy_uint32, u32) +NPYV_IMPL_AVX512_MEM_INT(npy_int32, s32) +NPYV_IMPL_AVX512_MEM_INT(npy_uint64, u64) +NPYV_IMPL_AVX512_MEM_INT(npy_int64, s64) + +// unaligned load +#define npyv_load_f32(PTR) _mm512_loadu_ps((const __m512*)(PTR)) +#define npyv_load_f64(PTR) _mm512_loadu_pd((const __m512d*)(PTR)) +// aligned load +#define npyv_loada_f32(PTR) _mm512_load_ps((const __m512*)(PTR)) +#define npyv_loada_f64(PTR) _mm512_load_pd((const __m512d*)(PTR)) +// load lower part +#if defined(_MSC_VER) && defined(_M_IX86) + #define npyv_loadl_f32(PTR) _mm512_castsi512_ps(npyv__loadl((const __m256i *)(PTR))) + #define npyv_loadl_f64(PTR) _mm512_castsi512_pd(npyv__loadl((const __m256i *)(PTR))) +#else + #define npyv_loadl_f32(PTR) _mm512_castps256_ps512(_mm256_loadu_ps(PTR)) + #define npyv_loadl_f64(PTR) _mm512_castpd256_pd512(_mm256_loadu_pd(PTR)) +#endif +// stream load +#define npyv_loads_f32(PTR) _mm512_castsi512_ps(npyv__loads(PTR)) +#define npyv_loads_f64(PTR) _mm512_castsi512_pd(npyv__loads(PTR)) +// unaligned store +#define npyv_store_f32 _mm512_storeu_ps +#define npyv_store_f64 _mm512_storeu_pd +// aligned store +#define npyv_storea_f32 _mm512_store_ps +#define npyv_storea_f64 _mm512_store_pd +// stream store +#define npyv_stores_f32 _mm512_stream_ps +#define npyv_stores_f64 _mm512_stream_pd +// store lower part +#define npyv_storel_f32(PTR, VEC) _mm256_storeu_ps(PTR, npyv512_lower_ps256(VEC)) +#define npyv_storel_f64(PTR, VEC) _mm256_storeu_pd(PTR, npyv512_lower_pd256(VEC)) +// store higher part +#define npyv_storeh_f32(PTR, VEC) _mm256_storeu_ps(PTR, npyv512_higher_ps256(VEC)) +#define npyv_storeh_f64(PTR, VEC) _mm256_storeu_pd(PTR, npyv512_higher_pd256(VEC)) + +#endif // _NPY_SIMD_AVX512_MEMORY_H diff --git a/numpy/core/src/common/simd/avx512/misc.h b/numpy/core/src/common/simd/avx512/misc.h new file mode 100644 index 000000000..4b6729b05 --- /dev/null +++ b/numpy/core/src/common/simd/avx512/misc.h @@ -0,0 +1,252 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX512_MISC_H +#define _NPY_SIMD_AVX512_MISC_H + +// set all lanes to zero +#define npyv_zero_u8 _mm512_setzero_si512 +#define npyv_zero_s8 _mm512_setzero_si512 +#define npyv_zero_u16 _mm512_setzero_si512 +#define npyv_zero_s16 _mm512_setzero_si512 +#define npyv_zero_u32 _mm512_setzero_si512 +#define npyv_zero_s32 _mm512_setzero_si512 +#define npyv_zero_u64 _mm512_setzero_si512 +#define npyv_zero_s64 _mm512_setzero_si512 +#define npyv_zero_f32 _mm512_setzero_ps +#define npyv_zero_f64 _mm512_setzero_pd + +// set all lanes to same value +#define npyv_setall_u8(VAL) _mm512_set1_epi8((char)VAL) +#define npyv_setall_s8(VAL) _mm512_set1_epi8((char)VAL) +#define npyv_setall_u16(VAL) _mm512_set1_epi16((short)VAL) +#define npyv_setall_s16(VAL) _mm512_set1_epi16((short)VAL) +#define npyv_setall_u32(VAL) _mm512_set1_epi32((int)VAL) +#define npyv_setall_s32(VAL) _mm512_set1_epi32(VAL) +#define npyv_setall_u64(VAL) _mm512_set1_epi64(VAL) +#define npyv_setall_s64(VAL) _mm512_set1_epi64(VAL) +#define npyv_setall_f32(VAL) _mm512_set1_ps(VAL) +#define npyv_setall_f64(VAL) _mm512_set1_pd(VAL) + +/** + * vector with specific values set to each lane and + * set a specific value to all remained lanes + * + * _mm512_set_epi8 and _mm512_set_epi16 are missing in many compilers + */ +NPY_FINLINE __m512i npyv__setr_epi8( + char i0, char i1, char i2, char i3, char i4, char i5, char i6, char i7, + char i8, char i9, char i10, char i11, char i12, char i13, char i14, char i15, + char i16, char i17, char i18, char i19, char i20, char i21, char i22, char i23, + char i24, char i25, char i26, char i27, char i28, char i29, char i30, char i31, + char i32, char i33, char i34, char i35, char i36, char i37, char i38, char i39, + char i40, char i41, char i42, char i43, char i44, char i45, char i46, char i47, + char i48, char i49, char i50, char i51, char i52, char i53, char i54, char i55, + char i56, char i57, char i58, char i59, char i60, char i61, char i62, char i63) +{ + const char NPY_DECL_ALIGNED(64) data[64] = { + i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15, + i16, i17, i18, i19, i20, i21, i22, i23, i24, i25, i26, i27, i28, i29, i30, i31, + i32, i33, i34, i35, i36, i37, i38, i39, i40, i41, i42, i43, i44, i45, i46, i47, + i48, i49, i50, i51, i52, i53, i54, i55, i56, i57, i58, i59, i60, i61, i62, i63 + }; + return _mm512_load_si512((const void*)data); +} +NPY_FINLINE __m512i npyv__setr_epi16( + short i0, short i1, short i2, short i3, short i4, short i5, short i6, short i7, + short i8, short i9, short i10, short i11, short i12, short i13, short i14, short i15, + short i16, short i17, short i18, short i19, short i20, short i21, short i22, short i23, + short i24, short i25, short i26, short i27, short i28, short i29, short i30, short i31) +{ + const short NPY_DECL_ALIGNED(64) data[32] = { + i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15, + i16, i17, i18, i19, i20, i21, i22, i23, i24, i25, i26, i27, i28, i29, i30, i31 + }; + return _mm512_load_si512((const void*)data); +} +// args that generated by NPYV__SET_FILL_* not going to expand if +// _mm512_setr_* are defined as macros. +NPY_FINLINE __m512i npyv__setr_epi32( + int i0, int i1, int i2, int i3, int i4, int i5, int i6, int i7, + int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15) +{ + return _mm512_setr_epi32(i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15); +} +NPY_FINLINE __m512i npyv__setr_epi64(npy_int64 i0, npy_int64 i1, npy_int64 i2, npy_int64 i3, + npy_int64 i4, npy_int64 i5, npy_int64 i6, npy_int64 i7) +{ + return _mm512_setr_epi64(i0, i1, i2, i3, i4, i5, i6, i7); +} + +NPY_FINLINE __m512 npyv__setr_ps( + float i0, float i1, float i2, float i3, float i4, float i5, float i6, float i7, + float i8, float i9, float i10, float i11, float i12, float i13, float i14, float i15) +{ + return _mm512_setr_ps(i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15); +} +NPY_FINLINE __m512d npyv__setr_pd(double i0, double i1, double i2, double i3, + double i4, double i5, double i6, double i7) +{ + return _mm512_setr_pd(i0, i1, i2, i3, i4, i5, i6, i7); +} +#define npyv_setf_u8(FILL, ...) npyv__setr_epi8(NPYV__SET_FILL_64(char, FILL, __VA_ARGS__)) +#define npyv_setf_s8(FILL, ...) npyv__setr_epi8(NPYV__SET_FILL_64(char, FILL, __VA_ARGS__)) +#define npyv_setf_u16(FILL, ...) npyv__setr_epi16(NPYV__SET_FILL_32(short, FILL, __VA_ARGS__)) +#define npyv_setf_s16(FILL, ...) npyv__setr_epi16(NPYV__SET_FILL_32(short, FILL, __VA_ARGS__)) +#define npyv_setf_u32(FILL, ...) npyv__setr_epi32(NPYV__SET_FILL_16(int, FILL, __VA_ARGS__)) +#define npyv_setf_s32(FILL, ...) npyv__setr_epi32(NPYV__SET_FILL_16(int, FILL, __VA_ARGS__)) +#define npyv_setf_u64(FILL, ...) npyv__setr_epi64(NPYV__SET_FILL_8(npy_int64, FILL, __VA_ARGS__)) +#define npyv_setf_s64(FILL, ...) npyv__setr_epi64(NPYV__SET_FILL_8(npy_int64, FILL, __VA_ARGS__)) +#define npyv_setf_f32(FILL, ...) npyv__setr_ps(NPYV__SET_FILL_16(float, FILL, __VA_ARGS__)) +#define npyv_setf_f64(FILL, ...) npyv__setr_pd(NPYV__SET_FILL_8(double, FILL, __VA_ARGS__)) + +// vector with specific values set to each lane and +// set zero to all remained lanes +#define npyv_set_u8(...) npyv_setf_u8(0, __VA_ARGS__) +#define npyv_set_s8(...) npyv_setf_s8(0, __VA_ARGS__) +#define npyv_set_u16(...) npyv_setf_u16(0, __VA_ARGS__) +#define npyv_set_s16(...) npyv_setf_s16(0, __VA_ARGS__) +#define npyv_set_u32(...) npyv_setf_u32(0, __VA_ARGS__) +#define npyv_set_s32(...) npyv_setf_s32(0, __VA_ARGS__) +#define npyv_set_u64(...) npyv_setf_u64(0, __VA_ARGS__) +#define npyv_set_s64(...) npyv_setf_s64(0, __VA_ARGS__) +#define npyv_set_f32(...) npyv_setf_f32(0, __VA_ARGS__) +#define npyv_set_f64(...) npyv_setf_f64(0, __VA_ARGS__) + +// per lane select +#ifdef NPY_HAVE_AVX512BW + #define npyv_select_u8(MASK, A, B) _mm512_mask_blend_epi8(MASK, B, A) + #define npyv_select_u16(MASK, A, B) _mm512_mask_blend_epi16(MASK, B, A) +#else + NPY_FINLINE __m512i npyv_select_u8(__m512i mask, __m512i a, __m512i b) + { return _mm512_xor_si512(b, _mm512_and_si512(_mm512_xor_si512(b, a), mask)); } + #define npyv_select_u16 npyv_select_u8 +#endif +#define npyv_select_s8 npyv_select_u8 +#define npyv_select_s16 npyv_select_u16 +#define npyv_select_u32(MASK, A, B) _mm512_mask_blend_epi32(MASK, B, A) +#define npyv_select_s32 npyv_select_u32 +#define npyv_select_u64(MASK, A, B) _mm512_mask_blend_epi64(MASK, B, A) +#define npyv_select_s64 npyv_select_u64 +#define npyv_select_f32(MASK, A, B) _mm512_mask_blend_ps(MASK, B, A) +#define npyv_select_f64(MASK, A, B) _mm512_mask_blend_pd(MASK, B, A) + +// reinterpret +#define npyv_reinterpret_u8_u8(X) X +#define npyv_reinterpret_u8_s8(X) X +#define npyv_reinterpret_u8_u16(X) X +#define npyv_reinterpret_u8_s16(X) X +#define npyv_reinterpret_u8_u32(X) X +#define npyv_reinterpret_u8_s32(X) X +#define npyv_reinterpret_u8_u64(X) X +#define npyv_reinterpret_u8_s64(X) X +#define npyv_reinterpret_u8_f32 _mm512_castps_si512 +#define npyv_reinterpret_u8_f64 _mm512_castpd_si512 + +#define npyv_reinterpret_s8_s8(X) X +#define npyv_reinterpret_s8_u8(X) X +#define npyv_reinterpret_s8_u16(X) X +#define npyv_reinterpret_s8_s16(X) X +#define npyv_reinterpret_s8_u32(X) X +#define npyv_reinterpret_s8_s32(X) X +#define npyv_reinterpret_s8_u64(X) X +#define npyv_reinterpret_s8_s64(X) X +#define npyv_reinterpret_s8_f32 _mm512_castps_si512 +#define npyv_reinterpret_s8_f64 _mm512_castpd_si512 + +#define npyv_reinterpret_u16_u16(X) X +#define npyv_reinterpret_u16_u8(X) X +#define npyv_reinterpret_u16_s8(X) X +#define npyv_reinterpret_u16_s16(X) X +#define npyv_reinterpret_u16_u32(X) X +#define npyv_reinterpret_u16_s32(X) X +#define npyv_reinterpret_u16_u64(X) X +#define npyv_reinterpret_u16_s64(X) X +#define npyv_reinterpret_u16_f32 _mm512_castps_si512 +#define npyv_reinterpret_u16_f64 _mm512_castpd_si512 + +#define npyv_reinterpret_s16_s16(X) X +#define npyv_reinterpret_s16_u8(X) X +#define npyv_reinterpret_s16_s8(X) X +#define npyv_reinterpret_s16_u16(X) X +#define npyv_reinterpret_s16_u32(X) X +#define npyv_reinterpret_s16_s32(X) X +#define npyv_reinterpret_s16_u64(X) X +#define npyv_reinterpret_s16_s64(X) X +#define npyv_reinterpret_s16_f32 _mm512_castps_si512 +#define npyv_reinterpret_s16_f64 _mm512_castpd_si512 + +#define npyv_reinterpret_u32_u32(X) X +#define npyv_reinterpret_u32_u8(X) X +#define npyv_reinterpret_u32_s8(X) X +#define npyv_reinterpret_u32_u16(X) X +#define npyv_reinterpret_u32_s16(X) X +#define npyv_reinterpret_u32_s32(X) X +#define npyv_reinterpret_u32_u64(X) X +#define npyv_reinterpret_u32_s64(X) X +#define npyv_reinterpret_u32_f32 _mm512_castps_si512 +#define npyv_reinterpret_u32_f64 _mm512_castpd_si512 + +#define npyv_reinterpret_s32_s32(X) X +#define npyv_reinterpret_s32_u8(X) X +#define npyv_reinterpret_s32_s8(X) X +#define npyv_reinterpret_s32_u16(X) X +#define npyv_reinterpret_s32_s16(X) X +#define npyv_reinterpret_s32_u32(X) X +#define npyv_reinterpret_s32_u64(X) X +#define npyv_reinterpret_s32_s64(X) X +#define npyv_reinterpret_s32_f32 _mm512_castps_si512 +#define npyv_reinterpret_s32_f64 _mm512_castpd_si512 + +#define npyv_reinterpret_u64_u64(X) X +#define npyv_reinterpret_u64_u8(X) X +#define npyv_reinterpret_u64_s8(X) X +#define npyv_reinterpret_u64_u16(X) X +#define npyv_reinterpret_u64_s16(X) X +#define npyv_reinterpret_u64_u32(X) X +#define npyv_reinterpret_u64_s32(X) X +#define npyv_reinterpret_u64_s64(X) X +#define npyv_reinterpret_u64_f32 _mm512_castps_si512 +#define npyv_reinterpret_u64_f64 _mm512_castpd_si512 + +#define npyv_reinterpret_s64_s64(X) X +#define npyv_reinterpret_s64_u8(X) X +#define npyv_reinterpret_s64_s8(X) X +#define npyv_reinterpret_s64_u16(X) X +#define npyv_reinterpret_s64_s16(X) X +#define npyv_reinterpret_s64_u32(X) X +#define npyv_reinterpret_s64_s32(X) X +#define npyv_reinterpret_s64_u64(X) X +#define npyv_reinterpret_s64_f32 _mm512_castps_si512 +#define npyv_reinterpret_s64_f64 _mm512_castpd_si512 + +#define npyv_reinterpret_f32_f32(X) X +#define npyv_reinterpret_f32_u8 _mm512_castsi512_ps +#define npyv_reinterpret_f32_s8 _mm512_castsi512_ps +#define npyv_reinterpret_f32_u16 _mm512_castsi512_ps +#define npyv_reinterpret_f32_s16 _mm512_castsi512_ps +#define npyv_reinterpret_f32_u32 _mm512_castsi512_ps +#define npyv_reinterpret_f32_s32 _mm512_castsi512_ps +#define npyv_reinterpret_f32_u64 _mm512_castsi512_ps +#define npyv_reinterpret_f32_s64 _mm512_castsi512_ps +#define npyv_reinterpret_f32_f64 _mm512_castpd_ps + +#define npyv_reinterpret_f64_f64(X) X +#define npyv_reinterpret_f64_u8 _mm512_castsi512_pd +#define npyv_reinterpret_f64_s8 _mm512_castsi512_pd +#define npyv_reinterpret_f64_u16 _mm512_castsi512_pd +#define npyv_reinterpret_f64_s16 _mm512_castsi512_pd +#define npyv_reinterpret_f64_u32 _mm512_castsi512_pd +#define npyv_reinterpret_f64_s32 _mm512_castsi512_pd +#define npyv_reinterpret_f64_u64 _mm512_castsi512_pd +#define npyv_reinterpret_f64_s64 _mm512_castsi512_pd +#define npyv_reinterpret_f64_f32 _mm512_castps_pd + +#ifdef NPY_HAVE_AVX512_KNL + #define npyv_cleanup() ((void)0) +#else + #define npyv_cleanup _mm256_zeroall +#endif + +#endif // _NPY_SIMD_AVX512_MISC_H diff --git a/numpy/core/src/common/simd/avx512/operators.h b/numpy/core/src/common/simd/avx512/operators.h new file mode 100644 index 000000000..f76ea5e2d --- /dev/null +++ b/numpy/core/src/common/simd/avx512/operators.h @@ -0,0 +1,259 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX512_OPERATORS_H +#define _NPY_SIMD_AVX512_OPERATORS_H + +/*************************** + * Shifting + ***************************/ + +// left +#ifdef NPY_HAVE_AVX512BW + #define npyv_shl_u16(A, C) _mm512_sll_epi16(A, _mm_cvtsi32_si128(C)) +#else + #define NPYV_IMPL_AVX512_SHIFT(FN, INTRIN) \ + NPY_FINLINE __m512i npyv_##FN(__m512i a, int c) \ + { \ + __m256i l = npyv512_lower_si256(a); \ + __m256i h = npyv512_higher_si256(a); \ + __m128i cv = _mm_cvtsi32_si128(c); \ + l = _mm256_##INTRIN(l, cv); \ + h = _mm256_##INTRIN(h, cv); \ + return npyv512_combine_si256(l, h); \ + } + + NPYV_IMPL_AVX512_SHIFT(shl_u16, sll_epi16) +#endif +#define npyv_shl_s16 npyv_shl_u16 +#define npyv_shl_u32(A, C) _mm512_sll_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_s32(A, C) _mm512_sll_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_u64(A, C) _mm512_sll_epi64(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_s64(A, C) _mm512_sll_epi64(A, _mm_cvtsi32_si128(C)) + +// left by an immediate constant +#ifdef NPY_HAVE_AVX512BW + #define npyv_shli_u16 _mm512_slli_epi16 +#else + #define npyv_shli_u16 npyv_shl_u16 +#endif +#define npyv_shli_s16 npyv_shl_u16 +#define npyv_shli_u32 _mm512_slli_epi32 +#define npyv_shli_s32 _mm512_slli_epi32 +#define npyv_shli_u64 _mm512_slli_epi64 +#define npyv_shli_s64 _mm512_slli_epi64 + +// right +#ifdef NPY_HAVE_AVX512BW + #define npyv_shr_u16(A, C) _mm512_srl_epi16(A, _mm_cvtsi32_si128(C)) + #define npyv_shr_s16(A, C) _mm512_sra_epi16(A, _mm_cvtsi32_si128(C)) +#else + NPYV_IMPL_AVX512_SHIFT(shr_u16, srl_epi16) + NPYV_IMPL_AVX512_SHIFT(shr_s16, sra_epi16) +#endif +#define npyv_shr_u32(A, C) _mm512_srl_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_s32(A, C) _mm512_sra_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_u64(A, C) _mm512_srl_epi64(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_s64(A, C) _mm512_sra_epi64(A, _mm_cvtsi32_si128(C)) + +// right by an immediate constant +#ifdef NPY_HAVE_AVX512BW + #define npyv_shri_u16 _mm512_srli_epi16 + #define npyv_shri_s16 _mm512_srai_epi16 +#else + #define npyv_shri_u16 npyv_shr_u16 + #define npyv_shri_s16 npyv_shr_s16 +#endif +#define npyv_shri_u32 _mm512_srli_epi32 +#define npyv_shri_s32 _mm512_srai_epi32 +#define npyv_shri_u64 _mm512_srli_epi64 +#define npyv_shri_s64 _mm512_srai_epi64 + +/*************************** + * Logical + ***************************/ + +// AND +#define npyv_and_u8 _mm512_and_si512 +#define npyv_and_s8 _mm512_and_si512 +#define npyv_and_u16 _mm512_and_si512 +#define npyv_and_s16 _mm512_and_si512 +#define npyv_and_u32 _mm512_and_si512 +#define npyv_and_s32 _mm512_and_si512 +#define npyv_and_u64 _mm512_and_si512 +#define npyv_and_s64 _mm512_and_si512 +#ifdef NPY_HAVE_AVX512DQ + #define npyv_and_f32 _mm512_and_ps + #define npyv_and_f64 _mm512_and_pd +#else + NPYV_IMPL_AVX512_FROM_SI512_PS_2ARG(npyv_and_f32, _mm512_and_si512) + NPYV_IMPL_AVX512_FROM_SI512_PD_2ARG(npyv_and_f64, _mm512_and_si512) +#endif + +// OR +#define npyv_or_u8 _mm512_or_si512 +#define npyv_or_s8 _mm512_or_si512 +#define npyv_or_u16 _mm512_or_si512 +#define npyv_or_s16 _mm512_or_si512 +#define npyv_or_u32 _mm512_or_si512 +#define npyv_or_s32 _mm512_or_si512 +#define npyv_or_u64 _mm512_or_si512 +#define npyv_or_s64 _mm512_or_si512 +#ifdef NPY_HAVE_AVX512DQ + #define npyv_or_f32 _mm512_or_ps + #define npyv_or_f64 _mm512_or_pd +#else + NPYV_IMPL_AVX512_FROM_SI512_PS_2ARG(npyv_or_f32, _mm512_or_si512) + NPYV_IMPL_AVX512_FROM_SI512_PD_2ARG(npyv_or_f64, _mm512_or_si512) +#endif + +// XOR +#define npyv_xor_u8 _mm512_xor_si512 +#define npyv_xor_s8 _mm512_xor_si512 +#define npyv_xor_u16 _mm512_xor_si512 +#define npyv_xor_s16 _mm512_xor_si512 +#define npyv_xor_u32 _mm512_xor_si512 +#define npyv_xor_s32 _mm512_xor_si512 +#define npyv_xor_u64 _mm512_xor_si512 +#define npyv_xor_s64 _mm512_xor_si512 +#ifdef NPY_HAVE_AVX512DQ + #define npyv_xor_f32 _mm512_xor_ps + #define npyv_xor_f64 _mm512_xor_pd +#else + NPYV_IMPL_AVX512_FROM_SI512_PS_2ARG(npyv_xor_f32, _mm512_xor_si512) + NPYV_IMPL_AVX512_FROM_SI512_PD_2ARG(npyv_xor_f64, _mm512_xor_si512) +#endif + +// NOT +#define npyv_not_u8(A) _mm512_xor_si512(A, _mm512_set1_epi32(-1)) +#define npyv_not_s8 npyv_not_u8 +#define npyv_not_u16 npyv_not_u8 +#define npyv_not_s16 npyv_not_u8 +#define npyv_not_u32 npyv_not_u8 +#define npyv_not_s32 npyv_not_u8 +#define npyv_not_u64 npyv_not_u8 +#define npyv_not_s64 npyv_not_u8 +#ifdef NPY_HAVE_AVX512DQ + #define npyv_not_f32(A) _mm512_xor_ps(A, _mm512_castsi512_ps(_mm512_set1_epi32(-1))) + #define npyv_not_f64(A) _mm512_xor_pd(A, _mm512_castsi512_pd(_mm512_set1_epi32(-1))) +#else + #define npyv_not_f32(A) _mm512_castsi512_ps(npyv_not_u32(_mm512_castps_si512(A))) + #define npyv_not_f64(A) _mm512_castsi512_pd(npyv_not_u64(_mm512_castpd_si512(A))) +#endif + +/*************************** + * Comparison + ***************************/ + +// int Equal +#ifdef NPY_HAVE_AVX512BW + #define npyv_cmpeq_u8 _mm512_cmpeq_epu8_mask + #define npyv_cmpeq_s8 _mm512_cmpeq_epi8_mask + #define npyv_cmpeq_u16 _mm512_cmpeq_epu16_mask + #define npyv_cmpeq_s16 _mm512_cmpeq_epi16_mask +#else + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_cmpeq_u8, _mm256_cmpeq_epi8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_cmpeq_u16, _mm256_cmpeq_epi16) + #define npyv_cmpeq_s8 npyv_cmpeq_u8 + #define npyv_cmpeq_s16 npyv_cmpeq_u16 +#endif +#define npyv_cmpeq_u32 _mm512_cmpeq_epu32_mask +#define npyv_cmpeq_s32 _mm512_cmpeq_epi32_mask +#define npyv_cmpeq_u64 _mm512_cmpeq_epu64_mask +#define npyv_cmpeq_s64 _mm512_cmpeq_epi64_mask + +// int not equal +#ifdef NPY_HAVE_AVX512BW + #define npyv_cmpneq_u8 _mm512_cmpneq_epu8_mask + #define npyv_cmpneq_s8 _mm512_cmpneq_epi8_mask + #define npyv_cmpneq_u16 _mm512_cmpneq_epu16_mask + #define npyv_cmpneq_s16 _mm512_cmpneq_epi16_mask +#else + #define npyv_cmpneq_u8(A, B) npyv_not_u8(npyv_cmpeq_u8(A, B)) + #define npyv_cmpneq_u16(A, B) npyv_not_u16(npyv_cmpeq_u16(A, B)) + #define npyv_cmpneq_s8 npyv_cmpneq_u8 + #define npyv_cmpneq_s16 npyv_cmpneq_u16 +#endif +#define npyv_cmpneq_u32 _mm512_cmpneq_epu32_mask +#define npyv_cmpneq_s32 _mm512_cmpneq_epi32_mask +#define npyv_cmpneq_u64 _mm512_cmpneq_epu64_mask +#define npyv_cmpneq_s64 _mm512_cmpneq_epi64_mask + +// greater than +#ifdef NPY_HAVE_AVX512BW + #define npyv_cmpgt_u8 _mm512_cmpgt_epu8_mask + #define npyv_cmpgt_s8 _mm512_cmpgt_epi8_mask + #define npyv_cmpgt_u16 _mm512_cmpgt_epu16_mask + #define npyv_cmpgt_s16 _mm512_cmpgt_epi16_mask +#else + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_cmpgt_s8, _mm256_cmpgt_epi8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv_cmpgt_s16, _mm256_cmpgt_epi16) + NPY_FINLINE __m512i npyv_cmpgt_u8(__m512i a, __m512i b) + { + const __m512i sbit = _mm512_set1_epi32(0x80808080); + return npyv_cmpgt_s8(_mm512_xor_si512(a, sbit), _mm512_xor_si512(b, sbit)); + } + NPY_FINLINE __m512i npyv_cmpgt_u16(__m512i a, __m512i b) + { + const __m512i sbit = _mm512_set1_epi32(0x80008000); + return npyv_cmpgt_s16(_mm512_xor_si512(a, sbit), _mm512_xor_si512(b, sbit)); + } +#endif +#define npyv_cmpgt_u32 _mm512_cmpgt_epu32_mask +#define npyv_cmpgt_s32 _mm512_cmpgt_epi32_mask +#define npyv_cmpgt_u64 _mm512_cmpgt_epu64_mask +#define npyv_cmpgt_s64 _mm512_cmpgt_epi64_mask + +// greater than or equal +#ifdef NPY_HAVE_AVX512BW + #define npyv_cmpge_u8 _mm512_cmpge_epu8_mask + #define npyv_cmpge_s8 _mm512_cmpge_epi8_mask + #define npyv_cmpge_u16 _mm512_cmpge_epu16_mask + #define npyv_cmpge_s16 _mm512_cmpge_epi16_mask +#else + #define npyv_cmpge_u8(A, B) npyv_not_u8(npyv_cmpgt_u8(B, A)) + #define npyv_cmpge_s8(A, B) npyv_not_s8(npyv_cmpgt_s8(B, A)) + #define npyv_cmpge_u16(A, B) npyv_not_u16(npyv_cmpgt_u16(B, A)) + #define npyv_cmpge_s16(A, B) npyv_not_s16(npyv_cmpgt_s16(B, A)) +#endif +#define npyv_cmpge_u32 _mm512_cmpge_epu32_mask +#define npyv_cmpge_s32 _mm512_cmpge_epi32_mask +#define npyv_cmpge_u64 _mm512_cmpge_epu64_mask +#define npyv_cmpge_s64 _mm512_cmpge_epi64_mask + +// less than +#define npyv_cmplt_u8(A, B) npyv_cmpgt_u8(B, A) +#define npyv_cmplt_s8(A, B) npyv_cmpgt_s8(B, A) +#define npyv_cmplt_u16(A, B) npyv_cmpgt_u16(B, A) +#define npyv_cmplt_s16(A, B) npyv_cmpgt_s16(B, A) +#define npyv_cmplt_u32(A, B) npyv_cmpgt_u32(B, A) +#define npyv_cmplt_s32(A, B) npyv_cmpgt_s32(B, A) +#define npyv_cmplt_u64(A, B) npyv_cmpgt_u64(B, A) +#define npyv_cmplt_s64(A, B) npyv_cmpgt_s64(B, A) + +// less than or equal +#define npyv_cmple_u8(A, B) npyv_cmpge_u8(B, A) +#define npyv_cmple_s8(A, B) npyv_cmpge_s8(B, A) +#define npyv_cmple_u16(A, B) npyv_cmpge_u16(B, A) +#define npyv_cmple_s16(A, B) npyv_cmpge_s16(B, A) +#define npyv_cmple_u32(A, B) npyv_cmpge_u32(B, A) +#define npyv_cmple_s32(A, B) npyv_cmpge_s32(B, A) +#define npyv_cmple_u64(A, B) npyv_cmpge_u64(B, A) +#define npyv_cmple_s64(A, B) npyv_cmpge_s64(B, A) + +// precision comparison +#define npyv_cmpeq_f32(A, B) _mm512_cmp_ps_mask(A, B, _CMP_EQ_OQ) +#define npyv_cmpeq_f64(A, B) _mm512_cmp_pd_mask(A, B, _CMP_EQ_OQ) +#define npyv_cmpneq_f32(A, B) _mm512_cmp_ps_mask(A, B, _CMP_NEQ_OQ) +#define npyv_cmpneq_f64(A, B) _mm512_cmp_pd_mask(A, B, _CMP_NEQ_OQ) +#define npyv_cmplt_f32(A, B) _mm512_cmp_ps_mask(A, B, _CMP_LT_OQ) +#define npyv_cmplt_f64(A, B) _mm512_cmp_pd_mask(A, B, _CMP_LT_OQ) +#define npyv_cmple_f32(A, B) _mm512_cmp_ps_mask(A, B, _CMP_LE_OQ) +#define npyv_cmple_f64(A, B) _mm512_cmp_pd_mask(A, B, _CMP_LE_OQ) +#define npyv_cmpgt_f32(A, B) _mm512_cmp_ps_mask(A, B, _CMP_GT_OQ) +#define npyv_cmpgt_f64(A, B) _mm512_cmp_pd_mask(A, B, _CMP_GT_OQ) +#define npyv_cmpge_f32(A, B) _mm512_cmp_ps_mask(A, B, _CMP_GE_OQ) +#define npyv_cmpge_f64(A, B) _mm512_cmp_pd_mask(A, B, _CMP_GE_OQ) + +#endif // _NPY_SIMD_AVX512_OPERATORS_H diff --git a/numpy/core/src/common/simd/avx512/reorder.h b/numpy/core/src/common/simd/avx512/reorder.h new file mode 100644 index 000000000..cdbae7aac --- /dev/null +++ b/numpy/core/src/common/simd/avx512/reorder.h @@ -0,0 +1,170 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX512_REORDER_H +#define _NPY_SIMD_AVX512_REORDER_H + +// combine lower part of two vectors +#define npyv_combinel_u8(A, B) _mm512_inserti64x4(A, _mm512_castsi512_si256(B), 1) +#define npyv_combinel_s8 npyv_combinel_u8 +#define npyv_combinel_u16 npyv_combinel_u8 +#define npyv_combinel_s16 npyv_combinel_u8 +#define npyv_combinel_u32 npyv_combinel_u8 +#define npyv_combinel_s32 npyv_combinel_u8 +#define npyv_combinel_u64 npyv_combinel_u8 +#define npyv_combinel_s64 npyv_combinel_u8 +#define npyv_combinel_f64(A, B) _mm512_insertf64x4(A, _mm512_castpd512_pd256(B), 1) +#ifdef NPY_HAVE_AVX512DQ + #define npyv_combinel_f32(A, B) \ + _mm512_insertf32x8(A, _mm512_castps512_ps256(B), 1) +#else + #define npyv_combinel_f32(A, B) \ + _mm512_castsi512_ps(npyv_combinel_u8(_mm512_castps_si512(A), _mm512_castps_si512(B))) +#endif + +// combine higher part of two vectors +#define npyv_combineh_u8(A, B) _mm512_inserti64x4(B, _mm512_extracti64x4_epi64(A, 1), 0) +#define npyv_combineh_s8 npyv_combineh_u8 +#define npyv_combineh_u16 npyv_combineh_u8 +#define npyv_combineh_s16 npyv_combineh_u8 +#define npyv_combineh_u32 npyv_combineh_u8 +#define npyv_combineh_s32 npyv_combineh_u8 +#define npyv_combineh_u64 npyv_combineh_u8 +#define npyv_combineh_s64 npyv_combineh_u8 +#define npyv_combineh_f64(A, B) _mm512_insertf64x4(B, _mm512_extractf64x4_pd(A, 1), 0) +#ifdef NPY_HAVE_AVX512DQ + #define npyv_combineh_f32(A, B) \ + _mm512_insertf32x8(B, _mm512_extractf32x8_ps(A, 1), 0) +#else + #define npyv_combineh_f32(A, B) \ + _mm512_castsi512_ps(npyv_combineh_u8(_mm512_castps_si512(A), _mm512_castps_si512(B))) +#endif + +// combine two vectors from lower and higher parts of two other vectors +NPY_FINLINE npyv_m512ix2 npyv__combine(__m512i a, __m512i b) +{ + npyv_m512ix2 r; + r.val[0] = npyv_combinel_u8(a, b); + r.val[1] = npyv_combineh_u8(a, b); + return r; +} +NPY_FINLINE npyv_f32x2 npyv_combine_f32(__m512 a, __m512 b) +{ + npyv_f32x2 r; + r.val[0] = npyv_combinel_f32(a, b); + r.val[1] = npyv_combineh_f32(a, b); + return r; +} +NPY_FINLINE npyv_f64x2 npyv_combine_f64(__m512d a, __m512d b) +{ + npyv_f64x2 r; + r.val[0] = npyv_combinel_f64(a, b); + r.val[1] = npyv_combineh_f64(a, b); + return r; +} +#define npyv_combine_u8 npyv__combine +#define npyv_combine_s8 npyv__combine +#define npyv_combine_u16 npyv__combine +#define npyv_combine_s16 npyv__combine +#define npyv_combine_u32 npyv__combine +#define npyv_combine_s32 npyv__combine +#define npyv_combine_u64 npyv__combine +#define npyv_combine_s64 npyv__combine + +// interleave two vectors +#ifndef NPY_HAVE_AVX512BW + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv__unpacklo_epi8, _mm256_unpacklo_epi8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv__unpackhi_epi8, _mm256_unpackhi_epi8) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv__unpacklo_epi16, _mm256_unpacklo_epi16) + NPYV_IMPL_AVX512_FROM_AVX2_2ARG(npyv__unpackhi_epi16, _mm256_unpackhi_epi16) +#endif + +NPY_FINLINE npyv_u64x2 npyv_zip_u64(__m512i a, __m512i b) +{ + npyv_u64x2 r; + r.val[0] = _mm512_permutex2var_epi64(a, npyv_set_u64(0, 8, 1, 9, 2, 10, 3, 11), b); + r.val[1] = _mm512_permutex2var_epi64(a, npyv_set_u64(4, 12, 5, 13, 6, 14, 7, 15), b); + return r; +} +#define npyv_zip_s64 npyv_zip_u64 + +NPY_FINLINE npyv_u8x2 npyv_zip_u8(__m512i a, __m512i b) +{ + npyv_u8x2 r; +#ifdef NPY_HAVE_AVX512VBMI + r.val[0] = _mm512_permutex2var_epi8(a, + npyv_set_u8(0, 64, 1, 65, 2, 66, 3, 67, 4, 68, 5, 69, 6, 70, 7, 71, + 8, 72, 9, 73, 10, 74, 11, 75, 12, 76, 13, 77, 14, 78, 15, 79, + 16, 80, 17, 81, 18, 82, 19, 83, 20, 84, 21, 85, 22, 86, 23, 87, + 24, 88, 25, 89, 26, 90, 27, 91, 28, 92, 29, 93, 30, 94, 31, 95), b); + r.val[1] = _mm512_permutex2var_epi8(a, + npyv_set_u8(32, 96, 33, 97, 34, 98, 35, 99, 36, 100, 37, 101, 38, 102, 39, 103, + 40, 104, 41, 105, 42, 106, 43, 107, 44, 108, 45, 109, 46, 110, 47, 111, + 48, 112, 49, 113, 50, 114, 51, 115, 52, 116, 53, 117, 54, 118, 55, 119, + 56, 120, 57, 121, 58, 122, 59, 123, 60, 124, 61, 125, 62, 126, 63, 127), b); +#else + #ifdef NPY_HAVE_AVX512BW + __m512i ab0 = _mm512_unpacklo_epi8(a, b); + __m512i ab1 = _mm512_unpackhi_epi8(a, b); + #else + __m512i ab0 = npyv__unpacklo_epi8(a, b); + __m512i ab1 = npyv__unpackhi_epi8(a, b); + #endif + r.val[0] = _mm512_permutex2var_epi64(ab0, npyv_set_u64(0, 1, 8, 9, 2, 3, 10, 11), ab1); + r.val[1] = _mm512_permutex2var_epi64(ab0, npyv_set_u64(4, 5, 12, 13, 6, 7, 14, 15), ab1); +#endif + return r; +} +#define npyv_zip_s8 npyv_zip_u8 + +NPY_FINLINE npyv_u16x2 npyv_zip_u16(__m512i a, __m512i b) +{ + npyv_u16x2 r; +#ifdef NPY_HAVE_AVX512BW + r.val[0] = _mm512_permutex2var_epi16(a, + npyv_set_u16(0, 32, 1, 33, 2, 34, 3, 35, 4, 36, 5, 37, 6, 38, 7, 39, + 8, 40, 9, 41, 10, 42, 11, 43, 12, 44, 13, 45, 14, 46, 15, 47), b); + r.val[1] = _mm512_permutex2var_epi16(a, + npyv_set_u16(16, 48, 17, 49, 18, 50, 19, 51, 20, 52, 21, 53, 22, 54, 23, 55, + 24, 56, 25, 57, 26, 58, 27, 59, 28, 60, 29, 61, 30, 62, 31, 63), b); +#else + __m512i ab0 = npyv__unpacklo_epi16(a, b); + __m512i ab1 = npyv__unpackhi_epi16(a, b); + r.val[0] = _mm512_permutex2var_epi64(ab0, npyv_set_u64(0, 1, 8, 9, 2, 3, 10, 11), ab1); + r.val[1] = _mm512_permutex2var_epi64(ab0, npyv_set_u64(4, 5, 12, 13, 6, 7, 14, 15), ab1); +#endif + return r; +} +#define npyv_zip_s16 npyv_zip_u16 + +NPY_FINLINE npyv_u32x2 npyv_zip_u32(__m512i a, __m512i b) +{ + npyv_u32x2 r; + r.val[0] = _mm512_permutex2var_epi32(a, + npyv_set_u32(0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23), b); + r.val[1] = _mm512_permutex2var_epi32(a, + npyv_set_u32(8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31), b); + return r; +} +#define npyv_zip_s32 npyv_zip_u32 + +NPY_FINLINE npyv_f32x2 npyv_zip_f32(__m512 a, __m512 b) +{ + npyv_f32x2 r; + r.val[0] = _mm512_permutex2var_ps(a, + npyv_set_u32(0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23), b); + r.val[1] = _mm512_permutex2var_ps(a, + npyv_set_u32(8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31), b); + return r; +} + +NPY_FINLINE npyv_f64x2 npyv_zip_f64(__m512d a, __m512d b) +{ + npyv_f64x2 r; + r.val[0] = _mm512_permutex2var_pd(a, npyv_set_u64(0, 8, 1, 9, 2, 10, 3, 11), b); + r.val[1] = _mm512_permutex2var_pd(a, npyv_set_u64(4, 12, 5, 13, 6, 14, 7, 15), b); + return r; +} + +#endif // _NPY_SIMD_AVX512_REORDER_H diff --git a/numpy/core/src/common/simd/avx512/utils.h b/numpy/core/src/common/simd/avx512/utils.h new file mode 100644 index 000000000..8066283c6 --- /dev/null +++ b/numpy/core/src/common/simd/avx512/utils.h @@ -0,0 +1,70 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_AVX512_UTILS_H +#define _NPY_SIMD_AVX512_UTILS_H + +#define npyv512_lower_si256 _mm512_castsi512_si256 +#define npyv512_lower_ps256 _mm512_castps512_ps256 +#define npyv512_lower_pd256 _mm512_castpd512_pd256 + +#define npyv512_higher_si256(A) _mm512_extracti64x4_epi64(A, 1) +#define npyv512_higher_pd256(A) _mm512_extractf64x4_pd(A, 1) + +#ifdef NPY_HAVE_AVX512DQ + #define npyv512_higher_ps256(A) _mm512_extractf32x8_ps(A, 1) +#else + #define npyv512_higher_ps256(A) \ + _mm256_castsi256_ps(_mm512_extracti64x4_epi64(_mm512_castps_si512(A), 1)) +#endif + +#define npyv512_combine_si256(A, B) _mm512_inserti64x4(_mm512_castsi256_si512(A), B, 1) +#define npyv512_combine_pd256(A, B) _mm512_insertf64x4(_mm512_castpd256_pd512(A), B, 1) + +#ifdef NPY_HAVE_AVX512DQ + #define npyv512_combine_ps256(A, B) _mm512_insertf32x8(_mm512_castps256_ps512(A), B, 1) +#else + #define npyv512_combine_ps256(A, B) \ + _mm512_castsi512_ps(npyv512_combine_si256(_mm512_castps_si512(A), _mm512_castps_si512(B))) +#endif + +#define NPYV_IMPL_AVX512_FROM_AVX2_1ARG(FN_NAME, INTRIN) \ + NPY_FINLINE __m512i FN_NAME(__m512i a) \ + { \ + __m256i l_a = npyv512_lower_si256(a); \ + __m256i h_a = npyv512_higher_si256(a); \ + l_a = INTRIN(l_a); \ + h_a = INTRIN(h_a); \ + return npyv512_combine_si256(l_a, h_a); \ + } + +#define NPYV_IMPL_AVX512_FROM_AVX2_2ARG(FN_NAME, INTRIN) \ + NPY_FINLINE __m512i FN_NAME(__m512i a, __m512i b) \ + { \ + __m256i l_a = npyv512_lower_si256(a); \ + __m256i h_a = npyv512_higher_si256(a); \ + __m256i l_b = npyv512_lower_si256(b); \ + __m256i h_b = npyv512_higher_si256(b); \ + l_a = INTRIN(l_a, l_b); \ + h_a = INTRIN(h_a, h_b); \ + return npyv512_combine_si256(l_a, h_a); \ + } + +#define NPYV_IMPL_AVX512_FROM_SI512_PS_2ARG(FN_NAME, INTRIN) \ + NPY_FINLINE __m512 FN_NAME(__m512 a, __m512 b) \ + { \ + return _mm512_castsi512_ps(INTRIN( \ + _mm512_castps_si512(a), _mm512_castps_si512(b) \ + )); \ + } + +#define NPYV_IMPL_AVX512_FROM_SI512_PD_2ARG(FN_NAME, INTRIN) \ + NPY_FINLINE __m512d FN_NAME(__m512d a, __m512d b) \ + { \ + return _mm512_castsi512_pd(INTRIN( \ + _mm512_castpd_si512(a), _mm512_castpd_si512(b) \ + )); \ + } + +#endif // _NPY_SIMD_AVX512_UTILS_H diff --git a/numpy/core/src/common/simd/neon/arithmetic.h b/numpy/core/src/common/simd/neon/arithmetic.h new file mode 100644 index 000000000..ec8b8ecd0 --- /dev/null +++ b/numpy/core/src/common/simd/neon/arithmetic.h @@ -0,0 +1,78 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_NEON_ARITHMETIC_H +#define _NPY_SIMD_NEON_ARITHMETIC_H + +/*************************** + * Addition + ***************************/ +// non-saturated +#define npyv_add_u8 vaddq_u8 +#define npyv_add_s8 vaddq_s8 +#define npyv_add_u16 vaddq_u16 +#define npyv_add_s16 vaddq_s16 +#define npyv_add_u32 vaddq_u32 +#define npyv_add_s32 vaddq_s32 +#define npyv_add_u64 vaddq_u64 +#define npyv_add_s64 vaddq_s64 +#define npyv_add_f32 vaddq_f32 +#define npyv_add_f64 vaddq_f64 + +// saturated +#define npyv_adds_u8 vqaddq_u8 +#define npyv_adds_s8 vqaddq_s8 +#define npyv_adds_u16 vqaddq_u16 +#define npyv_adds_s16 vqaddq_s16 + +/*************************** + * Subtraction + ***************************/ +// non-saturated +#define npyv_sub_u8 vsubq_u8 +#define npyv_sub_s8 vsubq_s8 +#define npyv_sub_u16 vsubq_u16 +#define npyv_sub_s16 vsubq_s16 +#define npyv_sub_u32 vsubq_u32 +#define npyv_sub_s32 vsubq_s32 +#define npyv_sub_u64 vsubq_u64 +#define npyv_sub_s64 vsubq_s64 +#define npyv_sub_f32 vsubq_f32 +#define npyv_sub_f64 vsubq_f64 + +// saturated +#define npyv_subs_u8 vqsubq_u8 +#define npyv_subs_s8 vqsubq_s8 +#define npyv_subs_u16 vqsubq_u16 +#define npyv_subs_s16 vqsubq_s16 + +/*************************** + * Multiplication + ***************************/ +// non-saturated +#define npyv_mul_u8 vmulq_u8 +#define npyv_mul_s8 vmulq_s8 +#define npyv_mul_u16 vmulq_u16 +#define npyv_mul_s16 vmulq_s16 +#define npyv_mul_u32 vmulq_u32 +#define npyv_mul_s32 vmulq_s32 +#define npyv_mul_f32 vmulq_f32 +#define npyv_mul_f64 vmulq_f64 + +/*************************** + * Division + ***************************/ +#ifdef __aarch64__ + #define npyv_div_f32 vdivq_f32 +#else + NPY_FINLINE float32x4_t npyv_div_f32(float32x4_t a, float32x4_t b) + { + float32x4_t recip = vrecpeq_f32(b); + recip = vmulq_f32(vrecpsq_f32(b, recip), recip); + return vmulq_f32(a, recip); + } +#endif +#define npyv_div_f64 vdivq_f64 + +#endif // _NPY_SIMD_NEON_ARITHMETIC_H diff --git a/numpy/core/src/common/simd/neon/conversion.h b/numpy/core/src/common/simd/neon/conversion.h new file mode 100644 index 000000000..b286931d1 --- /dev/null +++ b/numpy/core/src/common/simd/neon/conversion.h @@ -0,0 +1,32 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_NEON_CVT_H +#define _NPY_SIMD_NEON_CVT_H + +// convert boolean vectors to integer vectors +#define npyv_cvt_u8_b8(A) A +#define npyv_cvt_s8_b8(A) vreinterpretq_s8_u8(A) +#define npyv_cvt_u16_b16(A) A +#define npyv_cvt_s16_b16(A) vreinterpretq_s16_u16(A) +#define npyv_cvt_u32_b32(A) A +#define npyv_cvt_s32_b32(A) vreinterpretq_s32_u32(A) +#define npyv_cvt_u64_b64(A) A +#define npyv_cvt_s64_b64(A) vreinterpretq_s64_u64(A) +#define npyv_cvt_f32_b32(A) vreinterpretq_f32_u32(A) +#define npyv_cvt_f64_b64(A) vreinterpretq_f64_u64(A) + +// convert integer vectors to boolean vectors +#define npyv_cvt_b8_u8(BL) BL +#define npyv_cvt_b8_s8(BL) vreinterpretq_u8_s8(BL) +#define npyv_cvt_b16_u16(BL) BL +#define npyv_cvt_b16_s16(BL) vreinterpretq_u16_s16(BL) +#define npyv_cvt_b32_u32(BL) BL +#define npyv_cvt_b32_s32(BL) vreinterpretq_u32_s32(BL) +#define npyv_cvt_b64_u64(BL) BL +#define npyv_cvt_b64_s64(BL) vreinterpretq_u64_s64(BL) +#define npyv_cvt_b32_f32(BL) vreinterpretq_u32_f32(BL) +#define npyv_cvt_b64_f64(BL) vreinterpretq_u64_f64(BL) + +#endif // _NPY_SIMD_NEON_CVT_H diff --git a/numpy/core/src/common/simd/neon/memory.h b/numpy/core/src/common/simd/neon/memory.h new file mode 100644 index 000000000..afa703584 --- /dev/null +++ b/numpy/core/src/common/simd/neon/memory.h @@ -0,0 +1,49 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_NEON_MEMORY_H +#define _NPY_SIMD_NEON_MEMORY_H + +/*************************** + * load/store + ***************************/ +// GCC requires literal type definitions for pointers types otherwise it causes ambiguous errors +#define NPYV_IMPL_NEON_MEM(SFX, CTYPE) \ + NPY_FINLINE npyv_##SFX npyv_load_##SFX(const npyv_lanetype_##SFX *ptr) \ + { return vld1q_##SFX((const CTYPE*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loada_##SFX(const npyv_lanetype_##SFX *ptr) \ + { return vld1q_##SFX((const CTYPE*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loads_##SFX(const npyv_lanetype_##SFX *ptr) \ + { return vld1q_##SFX((const CTYPE*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loadl_##SFX(const npyv_lanetype_##SFX *ptr) \ + { \ + return vcombine_##SFX( \ + vld1_##SFX((const CTYPE*)ptr), vdup_n_##SFX(0) \ + ); \ + } \ + NPY_FINLINE void npyv_store_##SFX(npyv_lanetype_##SFX *ptr, npyv_##SFX vec) \ + { vst1q_##SFX((CTYPE*)ptr, vec); } \ + NPY_FINLINE void npyv_storea_##SFX(npyv_lanetype_##SFX *ptr, npyv_##SFX vec) \ + { vst1q_##SFX((CTYPE*)ptr, vec); } \ + NPY_FINLINE void npyv_stores_##SFX(npyv_lanetype_##SFX *ptr, npyv_##SFX vec) \ + { vst1q_##SFX((CTYPE*)ptr, vec); } \ + NPY_FINLINE void npyv_storel_##SFX(npyv_lanetype_##SFX *ptr, npyv_##SFX vec) \ + { vst1_##SFX((CTYPE*)ptr, vget_low_##SFX(vec)); } \ + NPY_FINLINE void npyv_storeh_##SFX(npyv_lanetype_##SFX *ptr, npyv_##SFX vec) \ + { vst1_##SFX((CTYPE*)ptr, vget_high_##SFX(vec)); } + +NPYV_IMPL_NEON_MEM(u8, uint8_t) +NPYV_IMPL_NEON_MEM(s8, int8_t) +NPYV_IMPL_NEON_MEM(u16, uint16_t) +NPYV_IMPL_NEON_MEM(s16, int16_t) +NPYV_IMPL_NEON_MEM(u32, uint32_t) +NPYV_IMPL_NEON_MEM(s32, int32_t) +NPYV_IMPL_NEON_MEM(u64, uint64_t) +NPYV_IMPL_NEON_MEM(s64, int64_t) +NPYV_IMPL_NEON_MEM(f32, float) +#if NPY_SIMD_F64 +NPYV_IMPL_NEON_MEM(f64, double) +#endif + +#endif // _NPY_SIMD_NEON_MEMORY_H diff --git a/numpy/core/src/common/simd/neon/misc.h b/numpy/core/src/common/simd/neon/misc.h new file mode 100644 index 000000000..51b0c3858 --- /dev/null +++ b/numpy/core/src/common/simd/neon/misc.h @@ -0,0 +1,255 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_NEON_MISC_H +#define _NPY_SIMD_NEON_MISC_H + +// vector with zero lanes +#define npyv_zero_u8() vreinterpretq_u8_s32(npyv_zero_s32()) +#define npyv_zero_s8() vreinterpretq_s8_s32(npyv_zero_s32()) +#define npyv_zero_u16() vreinterpretq_u16_s32(npyv_zero_s32()) +#define npyv_zero_s16() vreinterpretq_s16_s32(npyv_zero_s32()) +#define npyv_zero_u32() vdupq_n_u32((unsigned)0) +#define npyv_zero_s32() vdupq_n_s32((int)0) +#define npyv_zero_u64() vreinterpretq_u64_s32(npyv_zero_s32()) +#define npyv_zero_s64() vreinterpretq_s64_s32(npyv_zero_s32()) +#define npyv_zero_f32() vdupq_n_f32(0.0f) +#define npyv_zero_f64() vdupq_n_f64(0.0) + +// vector with a specific value set to all lanes +#define npyv_setall_u8 vdupq_n_u8 +#define npyv_setall_s8 vdupq_n_s8 +#define npyv_setall_u16 vdupq_n_u16 +#define npyv_setall_s16 vdupq_n_s16 +#define npyv_setall_u32 vdupq_n_u32 +#define npyv_setall_s32 vdupq_n_s32 +#define npyv_setall_u64 vdupq_n_u64 +#define npyv_setall_s64 vdupq_n_s64 +#define npyv_setall_f32 vdupq_n_f32 +#define npyv_setall_f64 vdupq_n_f64 + +// vector with specific values set to each lane and +// set a specific value to all remained lanes +NPY_FINLINE uint8x16_t npyv__set_u8(npy_uint8 i0, npy_uint8 i1, npy_uint8 i2, npy_uint8 i3, + npy_uint8 i4, npy_uint8 i5, npy_uint8 i6, npy_uint8 i7, npy_uint8 i8, npy_uint8 i9, + npy_uint8 i10, npy_uint8 i11, npy_uint8 i12, npy_uint8 i13, npy_uint8 i14, npy_uint8 i15) +{ + const uint8_t NPY_DECL_ALIGNED(16) data[16] = { + i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15 + }; + return vld1q_u8(data); +} +#define npyv_setf_u8(FILL, ...) npyv__set_u8(NPYV__SET_FILL_16(npy_uint8, FILL, __VA_ARGS__)) + +NPY_FINLINE int8x16_t npyv__set_s8(npy_int8 i0, npy_int8 i1, npy_int8 i2, npy_int8 i3, + npy_int8 i4, npy_int8 i5, npy_int8 i6, npy_int8 i7, npy_int8 i8, npy_int8 i9, + npy_int8 i10, npy_int8 i11, npy_int8 i12, npy_int8 i13, npy_int8 i14, npy_int8 i15) +{ + const int8_t NPY_DECL_ALIGNED(16) data[16] = { + i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15 + }; + return vld1q_s8(data); +} +#define npyv_setf_s8(FILL, ...) npyv__set_s8(NPYV__SET_FILL_16(npy_int8, FILL, __VA_ARGS__)) + +NPY_FINLINE uint16x8_t npyv__set_u16(npy_uint16 i0, npy_uint16 i1, npy_uint16 i2, npy_uint16 i3, + npy_uint16 i4, npy_uint16 i5, npy_uint16 i6, npy_uint16 i7) +{ + const uint16_t NPY_DECL_ALIGNED(16) data[8] = {i0, i1, i2, i3, i4, i5, i6, i7}; + return vld1q_u16(data); +} +#define npyv_setf_u16(FILL, ...) npyv__set_u16(NPYV__SET_FILL_8(npy_uint16, FILL, __VA_ARGS__)) + +NPY_FINLINE int16x8_t npyv__set_s16(npy_int16 i0, npy_int16 i1, npy_int16 i2, npy_int16 i3, + npy_int16 i4, npy_int16 i5, npy_int16 i6, npy_int16 i7) +{ + const int16_t NPY_DECL_ALIGNED(16) data[8] = {i0, i1, i2, i3, i4, i5, i6, i7}; + return vld1q_s16(data); +} +#define npyv_setf_s16(FILL, ...) npyv__set_s16(NPYV__SET_FILL_8(npy_int16, FILL, __VA_ARGS__)) + +NPY_FINLINE uint32x4_t npyv__set_u32(npy_uint32 i0, npy_uint32 i1, npy_uint32 i2, npy_uint32 i3) +{ + const uint32_t NPY_DECL_ALIGNED(16) data[4] = {i0, i1, i2, i3}; + return vld1q_u32(data); +} +#define npyv_setf_u32(FILL, ...) npyv__set_u32(NPYV__SET_FILL_4(npy_uint32, FILL, __VA_ARGS__)) + +NPY_FINLINE int32x4_t npyv__set_s32(npy_int32 i0, npy_int32 i1, npy_int32 i2, npy_int32 i3) +{ + const int32_t NPY_DECL_ALIGNED(16) data[4] = {i0, i1, i2, i3}; + return vld1q_s32(data); +} +#define npyv_setf_s32(FILL, ...) npyv__set_s32(NPYV__SET_FILL_4(npy_int32, FILL, __VA_ARGS__)) + +NPY_FINLINE uint64x2_t npyv__set_u64(npy_uint64 i0, npy_uint64 i1) +{ + const uint64_t NPY_DECL_ALIGNED(16) data[2] = {i0, i1}; + return vld1q_u64(data); +} +#define npyv_setf_u64(FILL, ...) npyv__set_u64(NPYV__SET_FILL_2(npy_int64, FILL, __VA_ARGS__)) + +NPY_FINLINE int64x2_t npyv__set_s64(npy_int64 i0, npy_int64 i1) +{ + const int64_t NPY_DECL_ALIGNED(16) data[2] = {i0, i1}; + return vld1q_s64(data); +} +#define npyv_setf_s64(FILL, ...) npyv__set_s64(NPYV__SET_FILL_2(npy_int64, FILL, __VA_ARGS__)) + +NPY_FINLINE float32x4_t npyv__set_f32(float i0, float i1, float i2, float i3) +{ + const float NPY_DECL_ALIGNED(16) data[4] = {i0, i1, i2, i3}; + return vld1q_f32(data); +} +#define npyv_setf_f32(FILL, ...) npyv__set_f32(NPYV__SET_FILL_4(float, FILL, __VA_ARGS__)) + +#ifdef __aarch64__ +NPY_FINLINE float64x2_t npyv__set_f64(double i0, double i1) +{ + const double NPY_DECL_ALIGNED(16) data[2] = {i0, i1}; + return vld1q_f64(data); +} +#define npyv_setf_f64(FILL, ...) npyv__set_f64(NPYV__SET_FILL_2(double, FILL, __VA_ARGS__)) +#endif + +// vector with specific values set to each lane and +// set zero to all remained lanes +#define npyv_set_u8(...) npyv_setf_u8(0, __VA_ARGS__) +#define npyv_set_s8(...) npyv_setf_s8(0, __VA_ARGS__) +#define npyv_set_u16(...) npyv_setf_u16(0, __VA_ARGS__) +#define npyv_set_s16(...) npyv_setf_s16(0, __VA_ARGS__) +#define npyv_set_u32(...) npyv_setf_u32(0, __VA_ARGS__) +#define npyv_set_s32(...) npyv_setf_s32(0, __VA_ARGS__) +#define npyv_set_u64(...) npyv_setf_u64(0, __VA_ARGS__) +#define npyv_set_s64(...) npyv_setf_s64(0, __VA_ARGS__) +#define npyv_set_f32(...) npyv_setf_f32(0, __VA_ARGS__) +#define npyv_set_f64(...) npyv_setf_f64(0, __VA_ARGS__) + +// Per lane select +#define npyv_select_u8 vbslq_u8 +#define npyv_select_s8 vbslq_s8 +#define npyv_select_u16 vbslq_u16 +#define npyv_select_s16 vbslq_s16 +#define npyv_select_u32 vbslq_u32 +#define npyv_select_s32 vbslq_s32 +#define npyv_select_u64 vbslq_u64 +#define npyv_select_s64 vbslq_s64 +#define npyv_select_f32 vbslq_f32 +#define npyv_select_f64 vbslq_f64 + +// Reinterpret +#define npyv_reinterpret_u8_u8(X) X +#define npyv_reinterpret_u8_s8 vreinterpretq_u8_s8 +#define npyv_reinterpret_u8_u16 vreinterpretq_u8_u16 +#define npyv_reinterpret_u8_s16 vreinterpretq_u8_s16 +#define npyv_reinterpret_u8_u32 vreinterpretq_u8_u32 +#define npyv_reinterpret_u8_s32 vreinterpretq_u8_s32 +#define npyv_reinterpret_u8_u64 vreinterpretq_u8_u64 +#define npyv_reinterpret_u8_s64 vreinterpretq_u8_s64 +#define npyv_reinterpret_u8_f32 vreinterpretq_u8_f32 +#define npyv_reinterpret_u8_f64 vreinterpretq_u8_f64 + +#define npyv_reinterpret_s8_s8(X) X +#define npyv_reinterpret_s8_u8 vreinterpretq_s8_u8 +#define npyv_reinterpret_s8_u16 vreinterpretq_s8_u16 +#define npyv_reinterpret_s8_s16 vreinterpretq_s8_s16 +#define npyv_reinterpret_s8_u32 vreinterpretq_s8_u32 +#define npyv_reinterpret_s8_s32 vreinterpretq_s8_s32 +#define npyv_reinterpret_s8_u64 vreinterpretq_s8_u64 +#define npyv_reinterpret_s8_s64 vreinterpretq_s8_s64 +#define npyv_reinterpret_s8_f32 vreinterpretq_s8_f32 +#define npyv_reinterpret_s8_f64 vreinterpretq_s8_f64 + +#define npyv_reinterpret_u16_u16(X) X +#define npyv_reinterpret_u16_u8 vreinterpretq_u16_u8 +#define npyv_reinterpret_u16_s8 vreinterpretq_u16_s8 +#define npyv_reinterpret_u16_s16 vreinterpretq_u16_s16 +#define npyv_reinterpret_u16_u32 vreinterpretq_u16_u32 +#define npyv_reinterpret_u16_s32 vreinterpretq_u16_s32 +#define npyv_reinterpret_u16_u64 vreinterpretq_u16_u64 +#define npyv_reinterpret_u16_s64 vreinterpretq_u16_s64 +#define npyv_reinterpret_u16_f32 vreinterpretq_u16_f32 +#define npyv_reinterpret_u16_f64 vreinterpretq_u16_f64 + +#define npyv_reinterpret_s16_s16(X) X +#define npyv_reinterpret_s16_u8 vreinterpretq_s16_u8 +#define npyv_reinterpret_s16_s8 vreinterpretq_s16_s8 +#define npyv_reinterpret_s16_u16 vreinterpretq_s16_u16 +#define npyv_reinterpret_s16_u32 vreinterpretq_s16_u32 +#define npyv_reinterpret_s16_s32 vreinterpretq_s16_s32 +#define npyv_reinterpret_s16_u64 vreinterpretq_s16_u64 +#define npyv_reinterpret_s16_s64 vreinterpretq_s16_s64 +#define npyv_reinterpret_s16_f32 vreinterpretq_s16_f32 +#define npyv_reinterpret_s16_f64 vreinterpretq_s16_f64 + +#define npyv_reinterpret_u32_u32(X) X +#define npyv_reinterpret_u32_u8 vreinterpretq_u32_u8 +#define npyv_reinterpret_u32_s8 vreinterpretq_u32_s8 +#define npyv_reinterpret_u32_u16 vreinterpretq_u32_u16 +#define npyv_reinterpret_u32_s16 vreinterpretq_u32_s16 +#define npyv_reinterpret_u32_s32 vreinterpretq_u32_s32 +#define npyv_reinterpret_u32_u64 vreinterpretq_u32_u64 +#define npyv_reinterpret_u32_s64 vreinterpretq_u32_s64 +#define npyv_reinterpret_u32_f32 vreinterpretq_u32_f32 +#define npyv_reinterpret_u32_f64 vreinterpretq_u32_f64 + +#define npyv_reinterpret_s32_s32(X) X +#define npyv_reinterpret_s32_u8 vreinterpretq_s32_u8 +#define npyv_reinterpret_s32_s8 vreinterpretq_s32_s8 +#define npyv_reinterpret_s32_u16 vreinterpretq_s32_u16 +#define npyv_reinterpret_s32_s16 vreinterpretq_s32_s16 +#define npyv_reinterpret_s32_u32 vreinterpretq_s32_u32 +#define npyv_reinterpret_s32_u64 vreinterpretq_s32_u64 +#define npyv_reinterpret_s32_s64 vreinterpretq_s32_s64 +#define npyv_reinterpret_s32_f32 vreinterpretq_s32_f32 +#define npyv_reinterpret_s32_f64 vreinterpretq_s32_f64 + +#define npyv_reinterpret_u64_u64(X) X +#define npyv_reinterpret_u64_u8 vreinterpretq_u64_u8 +#define npyv_reinterpret_u64_s8 vreinterpretq_u64_s8 +#define npyv_reinterpret_u64_u16 vreinterpretq_u64_u16 +#define npyv_reinterpret_u64_s16 vreinterpretq_u64_s16 +#define npyv_reinterpret_u64_u32 vreinterpretq_u64_u32 +#define npyv_reinterpret_u64_s32 vreinterpretq_u64_s32 +#define npyv_reinterpret_u64_s64 vreinterpretq_u64_s64 +#define npyv_reinterpret_u64_f32 vreinterpretq_u64_f32 +#define npyv_reinterpret_u64_f64 vreinterpretq_u64_f64 + +#define npyv_reinterpret_s64_s64(X) X +#define npyv_reinterpret_s64_u8 vreinterpretq_s64_u8 +#define npyv_reinterpret_s64_s8 vreinterpretq_s64_s8 +#define npyv_reinterpret_s64_u16 vreinterpretq_s64_u16 +#define npyv_reinterpret_s64_s16 vreinterpretq_s64_s16 +#define npyv_reinterpret_s64_u32 vreinterpretq_s64_u32 +#define npyv_reinterpret_s64_s32 vreinterpretq_s64_s32 +#define npyv_reinterpret_s64_u64 vreinterpretq_s64_u64 +#define npyv_reinterpret_s64_f32 vreinterpretq_s64_f32 +#define npyv_reinterpret_s64_f64 vreinterpretq_s64_f64 + +#define npyv_reinterpret_f32_f32(X) X +#define npyv_reinterpret_f32_u8 vreinterpretq_f32_u8 +#define npyv_reinterpret_f32_s8 vreinterpretq_f32_s8 +#define npyv_reinterpret_f32_u16 vreinterpretq_f32_u16 +#define npyv_reinterpret_f32_s16 vreinterpretq_f32_s16 +#define npyv_reinterpret_f32_u32 vreinterpretq_f32_u32 +#define npyv_reinterpret_f32_s32 vreinterpretq_f32_s32 +#define npyv_reinterpret_f32_u64 vreinterpretq_f32_u64 +#define npyv_reinterpret_f32_s64 vreinterpretq_f32_s64 +#define npyv_reinterpret_f32_f64 vreinterpretq_f32_f64 + +#define npyv_reinterpret_f64_f64(X) X +#define npyv_reinterpret_f64_u8 vreinterpretq_f64_u8 +#define npyv_reinterpret_f64_s8 vreinterpretq_f64_s8 +#define npyv_reinterpret_f64_u16 vreinterpretq_f64_u16 +#define npyv_reinterpret_f64_s16 vreinterpretq_f64_s16 +#define npyv_reinterpret_f64_u32 vreinterpretq_f64_u32 +#define npyv_reinterpret_f64_s32 vreinterpretq_f64_s32 +#define npyv_reinterpret_f64_u64 vreinterpretq_f64_u64 +#define npyv_reinterpret_f64_s64 vreinterpretq_f64_s64 +#define npyv_reinterpret_f64_f32 vreinterpretq_f64_f32 + +// Only required by AVX2/AVX512 +#define npyv_cleanup() ((void)0) + +#endif // _NPY_SIMD_NEON_MISC_H diff --git a/numpy/core/src/common/simd/neon/neon.h b/numpy/core/src/common/simd/neon/neon.h new file mode 100644 index 000000000..280a34297 --- /dev/null +++ b/numpy/core/src/common/simd/neon/neon.h @@ -0,0 +1,74 @@ +#ifndef _NPY_SIMD_H_ + #error "Not a standalone header" +#endif + +#define NPY_SIMD 128 +#define NPY_SIMD_WIDTH 16 + +#ifdef __aarch64__ + #define NPY_SIMD_F64 1 +#else + #define NPY_SIMD_F64 0 +#endif + +typedef uint8x16_t npyv_u8; +typedef int8x16_t npyv_s8; +typedef uint16x8_t npyv_u16; +typedef int16x8_t npyv_s16; +typedef uint32x4_t npyv_u32; +typedef int32x4_t npyv_s32; +typedef uint64x2_t npyv_u64; +typedef int64x2_t npyv_s64; +typedef float32x4_t npyv_f32; +#if NPY_SIMD_F64 +typedef float64x2_t npyv_f64; +#endif + +typedef uint8x16_t npyv_b8; +typedef uint16x8_t npyv_b16; +typedef uint32x4_t npyv_b32; +typedef uint64x2_t npyv_b64; + +typedef uint8x16x2_t npyv_u8x2; +typedef int8x16x2_t npyv_s8x2; +typedef uint16x8x2_t npyv_u16x2; +typedef int16x8x2_t npyv_s16x2; +typedef uint32x4x2_t npyv_u32x2; +typedef int32x4x2_t npyv_s32x2; +typedef uint64x2x2_t npyv_u64x2; +typedef int64x2x2_t npyv_s64x2; +typedef float32x4x2_t npyv_f32x2; +#if NPY_SIMD_F64 +typedef float64x2x2_t npyv_f64x2; +#endif + +typedef uint8x16x3_t npyv_u8x3; +typedef int8x16x3_t npyv_s8x3; +typedef uint16x8x3_t npyv_u16x3; +typedef int16x8x3_t npyv_s16x3; +typedef uint32x4x3_t npyv_u32x3; +typedef int32x4x3_t npyv_s32x3; +typedef uint64x2x3_t npyv_u64x3; +typedef int64x2x3_t npyv_s64x3; +typedef float32x4x3_t npyv_f32x3; +#if NPY_SIMD_F64 +typedef float64x2x3_t npyv_f64x3; +#endif + +#define npyv_nlanes_u8 16 +#define npyv_nlanes_s8 16 +#define npyv_nlanes_u16 8 +#define npyv_nlanes_s16 8 +#define npyv_nlanes_u32 4 +#define npyv_nlanes_s32 4 +#define npyv_nlanes_u64 2 +#define npyv_nlanes_s64 2 +#define npyv_nlanes_f32 4 +#define npyv_nlanes_f64 2 + +#include "memory.h" +#include "misc.h" +#include "reorder.h" +#include "operators.h" +#include "conversion.h" +#include "arithmetic.h" diff --git a/numpy/core/src/common/simd/neon/operators.h b/numpy/core/src/common/simd/neon/operators.h new file mode 100644 index 000000000..c1ad4ba12 --- /dev/null +++ b/numpy/core/src/common/simd/neon/operators.h @@ -0,0 +1,218 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_NEON_OPERATORS_H +#define _NPY_SIMD_NEON_OPERATORS_H + +/*************************** + * Shifting + ***************************/ + +// left +#define npyv_shl_u16(A, C) vshlq_u16(A, npyv_setall_s16(C)) +#define npyv_shl_s16(A, C) vshlq_s16(A, npyv_setall_s16(C)) +#define npyv_shl_u32(A, C) vshlq_u32(A, npyv_setall_s32(C)) +#define npyv_shl_s32(A, C) vshlq_s32(A, npyv_setall_s32(C)) +#define npyv_shl_u64(A, C) vshlq_u64(A, npyv_setall_s64(C)) +#define npyv_shl_s64(A, C) vshlq_s64(A, npyv_setall_s64(C)) + +// left by an immediate constant +#define npyv_shli_u16 vshlq_n_u16 +#define npyv_shli_s16 vshlq_n_s16 +#define npyv_shli_u32 vshlq_n_u32 +#define npyv_shli_s32 vshlq_n_s32 +#define npyv_shli_u64 vshlq_n_u64 +#define npyv_shli_s64 vshlq_n_s64 + +// right +#define npyv_shr_u16(A, C) vshlq_u16(A, npyv_setall_s16(-(C))) +#define npyv_shr_s16(A, C) vshlq_s16(A, npyv_setall_s16(-(C))) +#define npyv_shr_u32(A, C) vshlq_u32(A, npyv_setall_s32(-(C))) +#define npyv_shr_s32(A, C) vshlq_s32(A, npyv_setall_s32(-(C))) +#define npyv_shr_u64(A, C) vshlq_u64(A, npyv_setall_s64(-(C))) +#define npyv_shr_s64(A, C) vshlq_s64(A, npyv_setall_s64(-(C))) + +// right by an immediate constant +#define npyv_shri_u16(VEC, C) ((C) == 0 ? VEC : vshrq_n_u16(VEC, C)) +#define npyv_shri_s16(VEC, C) ((C) == 0 ? VEC : vshrq_n_s16(VEC, C)) +#define npyv_shri_u32(VEC, C) ((C) == 0 ? VEC : vshrq_n_u32(VEC, C)) +#define npyv_shri_s32(VEC, C) ((C) == 0 ? VEC : vshrq_n_s32(VEC, C)) +#define npyv_shri_u64(VEC, C) ((C) == 0 ? VEC : vshrq_n_u64(VEC, C)) +#define npyv_shri_s64(VEC, C) ((C) == 0 ? VEC : vshrq_n_s64(VEC, C)) + +/*************************** + * Logical + ***************************/ + +// AND +#define npyv_and_u8 vandq_u8 +#define npyv_and_s8 vandq_s8 +#define npyv_and_u16 vandq_u16 +#define npyv_and_s16 vandq_s16 +#define npyv_and_u32 vandq_u32 +#define npyv_and_s32 vandq_s32 +#define npyv_and_u64 vandq_u64 +#define npyv_and_s64 vandq_s64 +#define npyv_and_f32(A, B) \ + vreinterpretq_f32_u8(vandq_u8(vreinterpretq_u8_f32(A), vreinterpretq_u8_f32(B))) +#define npyv_and_f64(A, B) \ + vreinterpretq_f64_u8(vandq_u8(vreinterpretq_u8_f64(A), vreinterpretq_u8_f64(B))) + +// OR +#define npyv_or_u8 vorrq_u8 +#define npyv_or_s8 vorrq_s8 +#define npyv_or_u16 vorrq_u16 +#define npyv_or_s16 vorrq_s16 +#define npyv_or_u32 vorrq_u32 +#define npyv_or_s32 vorrq_s32 +#define npyv_or_u64 vorrq_u64 +#define npyv_or_s64 vorrq_s64 +#define npyv_or_f32(A, B) \ + vreinterpretq_f32_u8(vorrq_u8(vreinterpretq_u8_f32(A), vreinterpretq_u8_f32(B))) +#define npyv_or_f64(A, B) \ + vreinterpretq_f64_u8(vorrq_u8(vreinterpretq_u8_f64(A), vreinterpretq_u8_f64(B))) + +// XOR +#define npyv_xor_u8 veorq_u8 +#define npyv_xor_s8 veorq_s8 +#define npyv_xor_u16 veorq_u16 +#define npyv_xor_s16 veorq_s16 +#define npyv_xor_u32 veorq_u32 +#define npyv_xor_s32 veorq_s32 +#define npyv_xor_u64 veorq_u64 +#define npyv_xor_s64 veorq_s64 +#define npyv_xor_f32(A, B) \ + vreinterpretq_f32_u8(veorq_u8(vreinterpretq_u8_f32(A), vreinterpretq_u8_f32(B))) +#define npyv_xor_f64(A, B) \ + vreinterpretq_f64_u8(veorq_u8(vreinterpretq_u8_f64(A), vreinterpretq_u8_f64(B))) + +// NOT +#define npyv_not_u8 vmvnq_u8 +#define npyv_not_s8 vmvnq_s8 +#define npyv_not_u16 vmvnq_u16 +#define npyv_not_s16 vmvnq_s16 +#define npyv_not_u32 vmvnq_u32 +#define npyv_not_s32 vmvnq_s32 +#define npyv_not_u64(A) vreinterpretq_u64_u8(vmvnq_u8(vreinterpretq_u8_u64(A))) +#define npyv_not_s64(A) vreinterpretq_s64_u8(vmvnq_u8(vreinterpretq_u8_s64(A))) +#define npyv_not_f32(A) vreinterpretq_f32_u8(vmvnq_u8(vreinterpretq_u8_f32(A))) +#define npyv_not_f64(A) vreinterpretq_f64_u8(vmvnq_u8(vreinterpretq_u8_f64(A))) + +/*************************** + * Comparison + ***************************/ + +// equal +#define npyv_cmpeq_u8 vceqq_u8 +#define npyv_cmpeq_s8 vceqq_s8 +#define npyv_cmpeq_u16 vceqq_u16 +#define npyv_cmpeq_s16 vceqq_s16 +#define npyv_cmpeq_u32 vceqq_u32 +#define npyv_cmpeq_s32 vceqq_s32 +#define npyv_cmpeq_f32 vceqq_f32 +#define npyv_cmpeq_f64 vceqq_f64 + +#ifdef __aarch64__ + #define npyv_cmpeq_u64 vceqq_u64 + #define npyv_cmpeq_s64 vceqq_s64 +#else + NPY_FINLINE uint64x2_t npyv_cmpeq_u64(uint64x2_t a, uint64x2_t b) + { + uint64x2_t cmpeq = vreinterpretq_u64_u32(vceqq_u32( + vreinterpretq_u32_u64(a), vreinterpretq_u32_u64(b) + )); + uint64x2_t cmpeq_h = vshlq_n_u64(cmpeq, 32); + uint64x2_t test = vandq_u64(cmpeq, cmpeq_h); + return vreinterpretq_u64_s64(vshrq_n_s64(vreinterpretq_s64_u64(test), 32)); + } + #define npyv_cmpeq_s64(A, B) \ + npyv_cmpeq_u64(vreinterpretq_u64_s64(A), vreinterpretq_u64_s64(B)) +#endif + +// not Equal +#define npyv_cmpneq_u8(A, B) vmvnq_u8(vceqq_u8(A, B)) +#define npyv_cmpneq_s8(A, B) vmvnq_u8(vceqq_s8(A, B)) +#define npyv_cmpneq_u16(A, B) vmvnq_u16(vceqq_u16(A, B)) +#define npyv_cmpneq_s16(A, B) vmvnq_u16(vceqq_s16(A, B)) +#define npyv_cmpneq_u32(A, B) vmvnq_u32(vceqq_u32(A, B)) +#define npyv_cmpneq_s32(A, B) vmvnq_u32(vceqq_s32(A, B)) +#define npyv_cmpneq_u64(A, B) npyv_not_u64(npyv_cmpeq_u64(A, B)) +#define npyv_cmpneq_s64(A, B) npyv_not_u64(npyv_cmpeq_s64(A, B)) +#define npyv_cmpneq_f32(A, B) vmvnq_u32(vceqq_f32(A, B)) +#define npyv_cmpneq_f64(A, B) npyv_not_u64(vceqq_f64(A, B)) + +// greater than +#define npyv_cmpgt_u8 vcgtq_u8 +#define npyv_cmpgt_s8 vcgtq_s8 +#define npyv_cmpgt_u16 vcgtq_u16 +#define npyv_cmpgt_s16 vcgtq_s16 +#define npyv_cmpgt_u32 vcgtq_u32 +#define npyv_cmpgt_s32 vcgtq_s32 +#define npyv_cmpgt_f32 vcgtq_f32 +#define npyv_cmpgt_f64 vcgtq_f64 + +#ifdef __aarch64__ + #define npyv_cmpgt_u64 vcgtq_u64 + #define npyv_cmpgt_s64 vcgtq_s64 +#else + NPY_FINLINE uint64x2_t npyv_cmpgt_s64(int64x2_t a, int64x2_t b) + { + int64x2_t sub = vsubq_s64(b, a); + uint64x2_t nsame_sbit = vreinterpretq_u64_s64(veorq_s64(a, b)); + int64x2_t test = vbslq_s64(nsame_sbit, b, sub); + int64x2_t extend_sbit = vshrq_n_s64(test, 63); + return vreinterpretq_u64_s64(extend_sbit); + } + NPY_FINLINE uint64x2_t npyv_cmpgt_u64(uint64x2_t a, uint64x2_t b) + { + const uint64x2_t sbit = npyv_setall_u64(0x8000000000000000); + a = npyv_xor_u64(a, sbit); + b = npyv_xor_u64(b, sbit); + return npyv_cmpgt_s64(vreinterpretq_s64_u64(a), vreinterpretq_s64_u64(b)); + } +#endif + +// greater than or equal +#define npyv_cmpge_u8 vcgeq_u8 +#define npyv_cmpge_s8 vcgeq_s8 +#define npyv_cmpge_u16 vcgeq_u16 +#define npyv_cmpge_s16 vcgeq_s16 +#define npyv_cmpge_u32 vcgeq_u32 +#define npyv_cmpge_s32 vcgeq_s32 +#define npyv_cmpge_f32 vcgeq_f32 +#define npyv_cmpge_f64 vcgeq_f64 + +#ifdef __aarch64__ + #define npyv_cmpge_u64 vcgeq_u64 + #define npyv_cmpge_s64 vcgeq_s64 +#else + #define npyv_cmpge_u64(A, B) npyv_not_u64(npyv_cmpgt_u64(B, A)) + #define npyv_cmpge_s64(A, B) npyv_not_u64(npyv_cmpgt_s64(B, A)) +#endif + +// less than +#define npyv_cmplt_u8(A, B) npyv_cmpgt_u8(B, A) +#define npyv_cmplt_s8(A, B) npyv_cmpgt_s8(B, A) +#define npyv_cmplt_u16(A, B) npyv_cmpgt_u16(B, A) +#define npyv_cmplt_s16(A, B) npyv_cmpgt_s16(B, A) +#define npyv_cmplt_u32(A, B) npyv_cmpgt_u32(B, A) +#define npyv_cmplt_s32(A, B) npyv_cmpgt_s32(B, A) +#define npyv_cmplt_u64(A, B) npyv_cmpgt_u64(B, A) +#define npyv_cmplt_s64(A, B) npyv_cmpgt_s64(B, A) +#define npyv_cmplt_f32(A, B) npyv_cmpgt_f32(B, A) +#define npyv_cmplt_f64(A, B) npyv_cmpgt_f64(B, A) + +// less than or equal +#define npyv_cmple_u8(A, B) npyv_cmpge_u8(B, A) +#define npyv_cmple_s8(A, B) npyv_cmpge_s8(B, A) +#define npyv_cmple_u16(A, B) npyv_cmpge_u16(B, A) +#define npyv_cmple_s16(A, B) npyv_cmpge_s16(B, A) +#define npyv_cmple_u32(A, B) npyv_cmpge_u32(B, A) +#define npyv_cmple_s32(A, B) npyv_cmpge_s32(B, A) +#define npyv_cmple_u64(A, B) npyv_cmpge_u64(B, A) +#define npyv_cmple_s64(A, B) npyv_cmpge_s64(B, A) +#define npyv_cmple_f32(A, B) npyv_cmpge_f32(B, A) +#define npyv_cmple_f64(A, B) npyv_cmpge_f64(B, A) + +#endif // _NPY_SIMD_NEON_OPERATORS_H diff --git a/numpy/core/src/common/simd/neon/reorder.h b/numpy/core/src/common/simd/neon/reorder.h new file mode 100644 index 000000000..712a77982 --- /dev/null +++ b/numpy/core/src/common/simd/neon/reorder.h @@ -0,0 +1,110 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_NEON_REORDER_H +#define _NPY_SIMD_NEON_REORDER_H + +// combine lower part of two vectors +#ifdef __aarch64__ + #define npyv_combinel_u8(A, B) vreinterpretq_u8_u64(vzip1q_u64(vreinterpretq_u64_u8(A), vreinterpretq_u64_u8(B))) + #define npyv_combinel_s8(A, B) vreinterpretq_s8_u64(vzip1q_u64(vreinterpretq_u64_s8(A), vreinterpretq_u64_s8(B))) + #define npyv_combinel_u16(A, B) vreinterpretq_u16_u64(vzip1q_u64(vreinterpretq_u64_u16(A), vreinterpretq_u64_u16(B))) + #define npyv_combinel_s16(A, B) vreinterpretq_s16_u64(vzip1q_u64(vreinterpretq_u64_s16(A), vreinterpretq_u64_s16(B))) + #define npyv_combinel_u32(A, B) vreinterpretq_u32_u64(vzip1q_u64(vreinterpretq_u64_u32(A), vreinterpretq_u64_u32(B))) + #define npyv_combinel_s32(A, B) vreinterpretq_s32_u64(vzip1q_u64(vreinterpretq_u64_s32(A), vreinterpretq_u64_s32(B))) + #define npyv_combinel_u64 vzip1q_u64 + #define npyv_combinel_s64 vzip1q_s64 + #define npyv_combinel_f32(A, B) vreinterpretq_f32_u64(vzip1q_u64(vreinterpretq_u64_f32(A), vreinterpretq_u64_f32(B))) + #define npyv_combinel_f64 vzip1q_f64 +#else + #define npyv_combinel_u8(A, B) vcombine_u8(vget_low_u8(A), vget_low_u8(B)) + #define npyv_combinel_s8(A, B) vcombine_s8(vget_low_s8(A), vget_low_s8(B)) + #define npyv_combinel_u16(A, B) vcombine_u16(vget_low_u16(A), vget_low_u16(B)) + #define npyv_combinel_s16(A, B) vcombine_s16(vget_low_s16(A), vget_low_s16(B)) + #define npyv_combinel_u32(A, B) vcombine_u32(vget_low_u32(A), vget_low_u32(B)) + #define npyv_combinel_s32(A, B) vcombine_s32(vget_low_s32(A), vget_low_s32(B)) + #define npyv_combinel_u64(A, B) vcombine_u64(vget_low_u64(A), vget_low_u64(B)) + #define npyv_combinel_s64(A, B) vcombine_s64(vget_low_s64(A), vget_low_s64(B)) + #define npyv_combinel_f32(A, B) vcombine_f32(vget_low_f32(A), vget_low_f32(B)) +#endif + +// combine higher part of two vectors +#ifdef __aarch64__ + #define npyv_combineh_u8(A, B) vreinterpretq_u8_u64(vzip2q_u64(vreinterpretq_u64_u8(A), vreinterpretq_u64_u8(B))) + #define npyv_combineh_s8(A, B) vreinterpretq_s8_u64(vzip2q_u64(vreinterpretq_u64_s8(A), vreinterpretq_u64_s8(B))) + #define npyv_combineh_u16(A, B) vreinterpretq_u16_u64(vzip2q_u64(vreinterpretq_u64_u16(A), vreinterpretq_u64_u16(B))) + #define npyv_combineh_s16(A, B) vreinterpretq_s16_u64(vzip2q_u64(vreinterpretq_u64_s16(A), vreinterpretq_u64_s16(B))) + #define npyv_combineh_u32(A, B) vreinterpretq_u32_u64(vzip2q_u64(vreinterpretq_u64_u32(A), vreinterpretq_u64_u32(B))) + #define npyv_combineh_s32(A, B) vreinterpretq_s32_u64(vzip2q_u64(vreinterpretq_u64_s32(A), vreinterpretq_u64_s32(B))) + #define npyv_combineh_u64 vzip2q_u64 + #define npyv_combineh_s64 vzip2q_s64 + #define npyv_combineh_f32(A, B) vreinterpretq_f32_u64(vzip2q_u64(vreinterpretq_u64_f32(A), vreinterpretq_u64_f32(B))) + #define npyv_combineh_f64 vzip2q_f64 +#else + #define npyv_combineh_u8(A, B) vcombine_u8(vget_high_u8(A), vget_high_u8(B)) + #define npyv_combineh_s8(A, B) vcombine_s8(vget_high_s8(A), vget_high_s8(B)) + #define npyv_combineh_u16(A, B) vcombine_u16(vget_high_u16(A), vget_high_u16(B)) + #define npyv_combineh_s16(A, B) vcombine_s16(vget_high_s16(A), vget_high_s16(B)) + #define npyv_combineh_u32(A, B) vcombine_u32(vget_high_u32(A), vget_high_u32(B)) + #define npyv_combineh_s32(A, B) vcombine_s32(vget_high_s32(A), vget_high_s32(B)) + #define npyv_combineh_u64(A, B) vcombine_u64(vget_high_u64(A), vget_high_u64(B)) + #define npyv_combineh_s64(A, B) vcombine_s64(vget_high_s64(A), vget_high_s64(B)) + #define npyv_combineh_f32(A, B) vcombine_f32(vget_high_f32(A), vget_high_f32(B)) +#endif + +// combine two vectors from lower and higher parts of two other vectors +#define NPYV_IMPL_NEON_COMBINE(T_VEC, SFX) \ + NPY_FINLINE T_VEC##x2 npyv_combine_##SFX(T_VEC a, T_VEC b) \ + { \ + T_VEC##x2 r; \ + r.val[0] = NPY_CAT(npyv_combinel_, SFX)(a, b); \ + r.val[1] = NPY_CAT(npyv_combineh_, SFX)(a, b); \ + return r; \ + } + +NPYV_IMPL_NEON_COMBINE(npyv_u8, u8) +NPYV_IMPL_NEON_COMBINE(npyv_s8, s8) +NPYV_IMPL_NEON_COMBINE(npyv_u16, u16) +NPYV_IMPL_NEON_COMBINE(npyv_s16, s16) +NPYV_IMPL_NEON_COMBINE(npyv_u32, u32) +NPYV_IMPL_NEON_COMBINE(npyv_s32, s32) +NPYV_IMPL_NEON_COMBINE(npyv_u64, u64) +NPYV_IMPL_NEON_COMBINE(npyv_s64, s64) +NPYV_IMPL_NEON_COMBINE(npyv_f32, f32) +#ifdef __aarch64__ +NPYV_IMPL_NEON_COMBINE(npyv_f64, f64) +#endif + +// interleave two vectors +#define NPYV_IMPL_NEON_ZIP(T_VEC, SFX) \ + NPY_FINLINE T_VEC##x2 npyv_zip_##SFX(T_VEC a, T_VEC b) \ + { \ + T_VEC##x2 r; \ + r.val[0] = vzip1q_##SFX(a, b); \ + r.val[1] = vzip2q_##SFX(a, b); \ + return r; \ + } + +#ifdef __aarch64__ + NPYV_IMPL_NEON_ZIP(npyv_u8, u8) + NPYV_IMPL_NEON_ZIP(npyv_s8, s8) + NPYV_IMPL_NEON_ZIP(npyv_u16, u16) + NPYV_IMPL_NEON_ZIP(npyv_s16, s16) + NPYV_IMPL_NEON_ZIP(npyv_u32, u32) + NPYV_IMPL_NEON_ZIP(npyv_s32, s32) + NPYV_IMPL_NEON_ZIP(npyv_f32, f32) + NPYV_IMPL_NEON_ZIP(npyv_f64, f64) +#else + #define npyv_zip_u8 vzipq_u8 + #define npyv_zip_s8 vzipq_s8 + #define npyv_zip_u16 vzipq_u16 + #define npyv_zip_s16 vzipq_s16 + #define npyv_zip_u32 vzipq_u32 + #define npyv_zip_s32 vzipq_s32 + #define npyv_zip_f32 vzipq_f32 +#endif +#define npyv_zip_u64 npyv_combine_u64 +#define npyv_zip_s64 npyv_combine_s64 + +#endif // _NPY_SIMD_NEON_REORDER_H diff --git a/numpy/core/src/common/simd/simd.h b/numpy/core/src/common/simd/simd.h new file mode 100644 index 000000000..2f39c8427 --- /dev/null +++ b/numpy/core/src/common/simd/simd.h @@ -0,0 +1,56 @@ +#ifndef _NPY_SIMD_H_ +#define _NPY_SIMD_H_ +/** + * the NumPy C SIMD vectorization interface "NPYV" are types and functions intended + * to simplify vectorization of code on different platforms, currently supports + * the following SIMD extensions SSE, AVX2, AVX512, VSX and NEON. + * + * TODO: Add an independent sphinx doc. +*/ +#include "numpy/npy_common.h" +#include "npy_cpu_dispatch.h" +#include "simd_utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// lane type by intrin suffix +typedef npy_uint8 npyv_lanetype_u8; +typedef npy_int8 npyv_lanetype_s8; +typedef npy_uint16 npyv_lanetype_u16; +typedef npy_int16 npyv_lanetype_s16; +typedef npy_uint32 npyv_lanetype_u32; +typedef npy_int32 npyv_lanetype_s32; +typedef npy_uint64 npyv_lanetype_u64; +typedef npy_int64 npyv_lanetype_s64; +typedef float npyv_lanetype_f32; +typedef double npyv_lanetype_f64; + +#if defined(NPY_HAVE_AVX512F) && !defined(NPY_SIMD_FORCE_256) && !defined(NPY_SIMD_FORCE_128) + #include "avx512/avx512.h" +#elif defined(NPY_HAVE_AVX2) && !defined(NPY_SIMD_FORCE_128) + #include "avx2/avx2.h" +#elif defined(NPY_HAVE_SSE2) + #include "sse/sse.h" +#endif + +// TODO: Add support for VSX(2.06) and BE Mode +#if defined(NPY_HAVE_VSX2) && defined(__LITTLE_ENDIAN__) + #include "vsx/vsx.h" +#endif + +#ifdef NPY_HAVE_NEON + #include "neon/neon.h" +#endif + +#ifndef NPY_SIMD + #define NPY_SIMD 0 + #define NPY_SIMD_WIDTH 0 + #define NPY_SIMD_F64 0 +#endif + +#ifdef __cplusplus +} +#endif +#endif // _NPY_SIMD_H_ diff --git a/numpy/core/src/common/simd/simd_utils.h b/numpy/core/src/common/simd/simd_utils.h new file mode 100644 index 000000000..06c2f16f7 --- /dev/null +++ b/numpy/core/src/common/simd/simd_utils.h @@ -0,0 +1,48 @@ +#ifndef _NPY_SIMD_UTILS_H +#define _NPY_SIMD_UTILS_H + +#define NPYV__SET_2(CAST, I0, I1, ...) (CAST)(I0), (CAST)(I1) + +#define NPYV__SET_4(CAST, I0, I1, I2, I3, ...) \ + (CAST)(I0), (CAST)(I1), (CAST)(I2), (CAST)(I3) + +#define NPYV__SET_8(CAST, I0, I1, I2, I3, I4, I5, I6, I7, ...) \ + (CAST)(I0), (CAST)(I1), (CAST)(I2), (CAST)(I3), (CAST)(I4), (CAST)(I5), (CAST)(I6), (CAST)(I7) + +#define NPYV__SET_16(CAST, I0, I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15, ...) \ + NPYV__SET_8(CAST, I0, I1, I2, I3, I4, I5, I6, I7), \ + NPYV__SET_8(CAST, I8, I9, I10, I11, I12, I13, I14, I15) + +#define NPYV__SET_32(CAST, I0, I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15, \ +I16, I17, I18, I19, I20, I21, I22, I23, I24, I25, I26, I27, I28, I29, I30, I31, ...) \ + \ + NPYV__SET_16(CAST, I0, I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15), \ + NPYV__SET_16(CAST, I16, I17, I18, I19, I20, I21, I22, I23, I24, I25, I26, I27, I28, I29, I30, I31) + +#define NPYV__SET_64(CAST, I0, I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15, \ +I16, I17, I18, I19, I20, I21, I22, I23, I24, I25, I26, I27, I28, I29, I30, I31, \ +I32, I33, I34, I35, I36, I37, I38, I39, I40, I41, I42, I43, I44, I45, I46, I47, \ +I48, I49, I50, I51, I52, I53, I54, I55, I56, I57, I58, I59, I60, I61, I62, I63, ...) \ + \ + NPYV__SET_32(CAST, I0, I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15, \ +I16, I17, I18, I19, I20, I21, I22, I23, I24, I25, I26, I27, I28, I29, I30, I31), \ + NPYV__SET_32(CAST, I32, I33, I34, I35, I36, I37, I38, I39, I40, I41, I42, I43, I44, I45, I46, I47, \ +I48, I49, I50, I51, I52, I53, I54, I55, I56, I57, I58, I59, I60, I61, I62, I63) + +#define NPYV__SET_FILL_2(CAST, F, ...) NPY_EXPAND(NPYV__SET_2(CAST, __VA_ARGS__, F, F)) + +#define NPYV__SET_FILL_4(CAST, F, ...) NPY_EXPAND(NPYV__SET_4(CAST, __VA_ARGS__, F, F, F, F)) + +#define NPYV__SET_FILL_8(CAST, F, ...) NPY_EXPAND(NPYV__SET_8(CAST, __VA_ARGS__, F, F, F, F, F, F, F, F)) + +#define NPYV__SET_FILL_16(CAST, F, ...) NPY_EXPAND(NPYV__SET_16(CAST, __VA_ARGS__, \ + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F)) + +#define NPYV__SET_FILL_32(CAST, F, ...) NPY_EXPAND(NPYV__SET_32(CAST, __VA_ARGS__, \ + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F)) + +#define NPYV__SET_FILL_64(CAST, F, ...) NPY_EXPAND(NPYV__SET_64(CAST, __VA_ARGS__, \ + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, \ + F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F)) + +#endif // _NPY_SIMD_UTILS_H diff --git a/numpy/core/src/common/simd/sse/arithmetic.h b/numpy/core/src/common/simd/sse/arithmetic.h new file mode 100644 index 000000000..12d0af05c --- /dev/null +++ b/numpy/core/src/common/simd/sse/arithmetic.h @@ -0,0 +1,95 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_SSE_ARITHMETIC_H +#define _NPY_SIMD_SSE_ARITHMETIC_H + +/*************************** + * Addition + ***************************/ +// non-saturated +#define npyv_add_u8 _mm_add_epi8 +#define npyv_add_s8 _mm_add_epi8 +#define npyv_add_u16 _mm_add_epi16 +#define npyv_add_s16 _mm_add_epi16 +#define npyv_add_u32 _mm_add_epi32 +#define npyv_add_s32 _mm_add_epi32 +#define npyv_add_u64 _mm_add_epi64 +#define npyv_add_s64 _mm_add_epi64 +#define npyv_add_f32 _mm_add_ps +#define npyv_add_f64 _mm_add_pd + +// saturated +#define npyv_adds_u8 _mm_adds_epu8 +#define npyv_adds_s8 _mm_adds_epi8 +#define npyv_adds_u16 _mm_adds_epu16 +#define npyv_adds_s16 _mm_adds_epi16 +// TODO: rest, after implment Packs intrins + +/*************************** + * Subtraction + ***************************/ +// non-saturated +#define npyv_sub_u8 _mm_sub_epi8 +#define npyv_sub_s8 _mm_sub_epi8 +#define npyv_sub_u16 _mm_sub_epi16 +#define npyv_sub_s16 _mm_sub_epi16 +#define npyv_sub_u32 _mm_sub_epi32 +#define npyv_sub_s32 _mm_sub_epi32 +#define npyv_sub_u64 _mm_sub_epi64 +#define npyv_sub_s64 _mm_sub_epi64 +#define npyv_sub_f32 _mm_sub_ps +#define npyv_sub_f64 _mm_sub_pd + +// saturated +#define npyv_subs_u8 _mm_subs_epu8 +#define npyv_subs_s8 _mm_subs_epi8 +#define npyv_subs_u16 _mm_subs_epu16 +#define npyv_subs_s16 _mm_subs_epi16 +// TODO: rest, after implment Packs intrins + +/*************************** + * Multiplication + ***************************/ +// non-saturated +NPY_FINLINE __m128i npyv_mul_u8(__m128i a, __m128i b) +{ + const __m128i mask = _mm_set1_epi32(0xFF00FF00); + __m128i even = _mm_mullo_epi16(a, b); + __m128i odd = _mm_mullo_epi16(_mm_srai_epi16(a, 8), _mm_srai_epi16(b, 8)); + odd = _mm_slli_epi16(odd, 8); + return npyv_select_u8(mask, odd, even); +} +#define npyv_mul_s8 npyv_mul_u8 +#define npyv_mul_u16 _mm_mullo_epi16 +#define npyv_mul_s16 _mm_mullo_epi16 + +#ifdef NPY_HAVE_SSE41 + #define npyv_mul_u32 _mm_mullo_epi32 +#else + NPY_FINLINE __m128i npyv_mul_u32(__m128i a, __m128i b) + { + __m128i even = _mm_mul_epu32(a, b); + __m128i odd = _mm_mul_epu32(_mm_srli_epi64(a, 32), _mm_srli_epi64(b, 32)); + __m128i low = _mm_unpacklo_epi32(even, odd); + __m128i high = _mm_unpackhi_epi32(even, odd); + return _mm_unpacklo_epi64(low, high); + } +#endif // NPY_HAVE_SSE41 +#define npyv_mul_s32 npyv_mul_u32 +// TODO: emulate 64-bit*/ +#define npyv_mul_f32 _mm_mul_ps +#define npyv_mul_f64 _mm_mul_pd + +// saturated +// TODO: after implment Packs intrins + +/*************************** + * Division + ***************************/ +// TODO: emulate integer division +#define npyv_div_f32 _mm_div_ps +#define npyv_div_f64 _mm_div_pd + +#endif // _NPY_SIMD_SSE_ARITHMETIC_H diff --git a/numpy/core/src/common/simd/sse/conversion.h b/numpy/core/src/common/simd/sse/conversion.h new file mode 100644 index 000000000..ea9660d13 --- /dev/null +++ b/numpy/core/src/common/simd/sse/conversion.h @@ -0,0 +1,32 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_SSE_CVT_H +#define _NPY_SIMD_SSE_CVT_H + +// convert mask types to integer types +#define npyv_cvt_u8_b8(BL) BL +#define npyv_cvt_s8_b8(BL) BL +#define npyv_cvt_u16_b16(BL) BL +#define npyv_cvt_s16_b16(BL) BL +#define npyv_cvt_u32_b32(BL) BL +#define npyv_cvt_s32_b32(BL) BL +#define npyv_cvt_u64_b64(BL) BL +#define npyv_cvt_s64_b64(BL) BL +#define npyv_cvt_f32_b32(BL) _mm_castsi128_ps(BL) +#define npyv_cvt_f64_b64(BL) _mm_castsi128_pd(BL) + +// convert integer types to mask types +#define npyv_cvt_b8_u8(A) A +#define npyv_cvt_b8_s8(A) A +#define npyv_cvt_b16_u16(A) A +#define npyv_cvt_b16_s16(A) A +#define npyv_cvt_b32_u32(A) A +#define npyv_cvt_b32_s32(A) A +#define npyv_cvt_b64_u64(A) A +#define npyv_cvt_b64_s64(A) A +#define npyv_cvt_b32_f32(A) _mm_castps_si128(A) +#define npyv_cvt_b64_f64(A) _mm_castpd_si128(A) + +#endif // _NPY_SIMD_SSE_CVT_H diff --git a/numpy/core/src/common/simd/sse/memory.h b/numpy/core/src/common/simd/sse/memory.h new file mode 100644 index 000000000..1a555d6f0 --- /dev/null +++ b/numpy/core/src/common/simd/sse/memory.h @@ -0,0 +1,74 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_SSE_MEMORY_H +#define _NPY_SIMD_SSE_MEMORY_H + +/*************************** + * load/store + ***************************/ +// stream load +#ifdef NPY_HAVE_SSE41 + #define npyv__loads(PTR) _mm_stream_load_si128((__m128i *)(PTR)) +#else + #define npyv__loads(PTR) _mm_load_si128((const __m128i *)(PTR)) +#endif +#define NPYV_IMPL_SSE_MEM_INT(CTYPE, SFX) \ + NPY_FINLINE npyv_##SFX npyv_load_##SFX(const CTYPE *ptr) \ + { return _mm_loadu_si128((const __m128i*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loada_##SFX(const CTYPE *ptr) \ + { return _mm_load_si128((const __m128i*)ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loads_##SFX(const CTYPE *ptr) \ + { return npyv__loads(ptr); } \ + NPY_FINLINE npyv_##SFX npyv_loadl_##SFX(const CTYPE *ptr) \ + { return _mm_loadl_epi64((const __m128i*)ptr); } \ + NPY_FINLINE void npyv_store_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm_storeu_si128((__m128i*)ptr, vec); } \ + NPY_FINLINE void npyv_storea_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm_store_si128((__m128i*)ptr, vec); } \ + NPY_FINLINE void npyv_stores_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm_stream_si128((__m128i*)ptr, vec); } \ + NPY_FINLINE void npyv_storel_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm_storel_epi64((__m128i *)ptr, vec); } \ + NPY_FINLINE void npyv_storeh_##SFX(CTYPE *ptr, npyv_##SFX vec) \ + { _mm_storel_epi64((__m128i *)ptr, _mm_unpackhi_epi64(vec, vec)); } + +NPYV_IMPL_SSE_MEM_INT(npy_uint8, u8) +NPYV_IMPL_SSE_MEM_INT(npy_int8, s8) +NPYV_IMPL_SSE_MEM_INT(npy_uint16, u16) +NPYV_IMPL_SSE_MEM_INT(npy_int16, s16) +NPYV_IMPL_SSE_MEM_INT(npy_uint32, u32) +NPYV_IMPL_SSE_MEM_INT(npy_int32, s32) +NPYV_IMPL_SSE_MEM_INT(npy_uint64, u64) +NPYV_IMPL_SSE_MEM_INT(npy_int64, s64) + +// unaligned load +#define npyv_load_f32 _mm_loadu_ps +#define npyv_load_f64 _mm_loadu_pd +// aligned load +#define npyv_loada_f32 _mm_load_ps +#define npyv_loada_f64 _mm_load_pd +// load lower part +#define npyv_loadl_f32(PTR) _mm_castsi128_ps(npyv_loadl_u32((const npy_uint32*)(PTR))) +#define npyv_loadl_f64(PTR) _mm_castsi128_pd(npyv_loadl_u32((const npy_uint32*)(PTR))) +// stream load +#define npyv_loads_f32(PTR) _mm_castsi128_ps(npyv__loads(PTR)) +#define npyv_loads_f64(PTR) _mm_castsi128_pd(npyv__loads(PTR)) +// unaligned store +#define npyv_store_f32 _mm_storeu_ps +#define npyv_store_f64 _mm_storeu_pd +// aligned store +#define npyv_storea_f32 _mm_store_ps +#define npyv_storea_f64 _mm_store_pd +// stream store +#define npyv_stores_f32 _mm_stream_ps +#define npyv_stores_f64 _mm_stream_pd +// store lower part +#define npyv_storel_f32(PTR, VEC) _mm_storel_epi64((__m128i*)(PTR), _mm_castps_si128(VEC)); +#define npyv_storel_f64(PTR, VEC) _mm_storel_epi64((__m128i*)(PTR), _mm_castpd_si128(VEC)); +// store higher part +#define npyv_storeh_f32(PTR, VEC) npyv_storeh_u32((npy_uint32*)(PTR), _mm_castps_si128(VEC)) +#define npyv_storeh_f64(PTR, VEC) npyv_storeh_u32((npy_uint32*)(PTR), _mm_castpd_si128(VEC)) + +#endif // _NPY_SIMD_SSE_MEMORY_H diff --git a/numpy/core/src/common/simd/sse/misc.h b/numpy/core/src/common/simd/sse/misc.h new file mode 100644 index 000000000..7ba47bc68 --- /dev/null +++ b/numpy/core/src/common/simd/sse/misc.h @@ -0,0 +1,230 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_SSE_MISC_H +#define _NPY_SIMD_SSE_MISC_H + +// vector with zero lanes +#define npyv_zero_u8 _mm_setzero_si128 +#define npyv_zero_s8 _mm_setzero_si128 +#define npyv_zero_u16 _mm_setzero_si128 +#define npyv_zero_s16 _mm_setzero_si128 +#define npyv_zero_u32 _mm_setzero_si128 +#define npyv_zero_s32 _mm_setzero_si128 +#define npyv_zero_u64 _mm_setzero_si128 +#define npyv_zero_s64 _mm_setzero_si128 +#define npyv_zero_f32 _mm_setzero_ps +#define npyv_zero_f64 _mm_setzero_pd + +// vector with a specific value set to all lanes +#define npyv_setall_u8(VAL) _mm_set1_epi8((char)VAL) +#define npyv_setall_s8(VAL) _mm_set1_epi8((char)VAL) +#define npyv_setall_u16(VAL) _mm_set1_epi16((short)VAL) +#define npyv_setall_s16(VAL) _mm_set1_epi16((short)VAL) +#define npyv_setall_u32(VAL) _mm_set1_epi32((int)VAL) +#define npyv_setall_s32(VAL) _mm_set1_epi32(VAL) +#if !defined(__x86_64__) && !defined(_M_X64) + #define npyv_setall_u64(VAL) _mm_set_epi32((int)(VAL >> 32), (int)VAL, (int)(VAL >> 32), (int)VAL) + #define npyv_setall_s64 npyv_setall_u64 +#else + #define npyv_setall_u64(VAL) _mm_set1_epi64x(VAL) + #define npyv_setall_s64(VAL) _mm_set1_epi64x(VAL) +#endif +#define npyv_setall_f32(VAL) _mm_set1_ps(VAL) +#define npyv_setall_f64(VAL) _mm_set1_pd(VAL) + +/** + * vector with specific values set to each lane and + * set a specific value to all remained lanes + * + * Args that generated by NPYV__SET_FILL_* not going to expand if + * _mm_setr_* are defined as macros. + */ +NPY_FINLINE __m128i npyv__setr_epi8( + char i0, char i1, char i2, char i3, char i4, char i5, char i6, char i7, + char i8, char i9, char i10, char i11, char i12, char i13, char i14, char i15) +{ + return _mm_setr_epi8(i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15); +} +NPY_FINLINE __m128i npyv__setr_epi16(short i0, short i1, short i2, short i3, short i4, short i5, + short i6, short i7) +{ + return _mm_setr_epi16(i0, i1, i2, i3, i4, i5, i6, i7); +} +NPY_FINLINE __m128i npyv__setr_epi32(int i0, int i1, int i2, int i3) +{ + return _mm_setr_epi32(i0, i1, i2, i3); +} +NPY_FINLINE __m128i npyv__setr_epi64(npy_int64 i0, npy_int64 i1) +{ + return _mm_set_epi64x(i1, i0); +} +NPY_FINLINE __m128 npyv__setr_ps(float i0, float i1, float i2, float i3) +{ + return _mm_setr_ps(i0, i1, i2, i3); +} +NPY_FINLINE __m128d npyv__setr_pd(double i0, double i1) +{ + return _mm_setr_pd(i0, i1); +} +#define npyv_setf_u8(FILL, ...) npyv__setr_epi8(NPYV__SET_FILL_16(char, FILL, __VA_ARGS__)) +#define npyv_setf_s8(FILL, ...) npyv__setr_epi8(NPYV__SET_FILL_16(char, FILL, __VA_ARGS__)) +#define npyv_setf_u16(FILL, ...) npyv__setr_epi16(NPYV__SET_FILL_8(short, FILL, __VA_ARGS__)) +#define npyv_setf_s16(FILL, ...) npyv__setr_epi16(NPYV__SET_FILL_8(short, FILL, __VA_ARGS__)) +#define npyv_setf_u32(FILL, ...) npyv__setr_epi32(NPYV__SET_FILL_4(int, FILL, __VA_ARGS__)) +#define npyv_setf_s32(FILL, ...) npyv__setr_epi32(NPYV__SET_FILL_4(int, FILL, __VA_ARGS__)) +#define npyv_setf_u64(FILL, ...) npyv__setr_epi64(NPYV__SET_FILL_2(npy_int64, FILL, __VA_ARGS__)) +#define npyv_setf_s64(FILL, ...) npyv__setr_epi64(NPYV__SET_FILL_2(npy_int64, FILL, __VA_ARGS__)) +#define npyv_setf_f32(FILL, ...) npyv__setr_ps(NPYV__SET_FILL_4(float, FILL, __VA_ARGS__)) +#define npyv_setf_f64(FILL, ...) npyv__setr_pd(NPYV__SET_FILL_2(double, FILL, __VA_ARGS__)) + +// vector with specific values set to each lane and +// set zero to all remained lanes +#define npyv_set_u8(...) npyv_setf_u8(0, __VA_ARGS__) +#define npyv_set_s8(...) npyv_setf_s8(0, __VA_ARGS__) +#define npyv_set_u16(...) npyv_setf_u16(0, __VA_ARGS__) +#define npyv_set_s16(...) npyv_setf_s16(0, __VA_ARGS__) +#define npyv_set_u32(...) npyv_setf_u32(0, __VA_ARGS__) +#define npyv_set_s32(...) npyv_setf_s32(0, __VA_ARGS__) +#define npyv_set_u64(...) npyv_setf_u64(0, __VA_ARGS__) +#define npyv_set_s64(...) npyv_setf_s64(0, __VA_ARGS__) +#define npyv_set_f32(...) npyv_setf_f32(0, __VA_ARGS__) +#define npyv_set_f64(...) npyv_setf_f64(0, __VA_ARGS__) + +// Per lane select +#ifdef NPY_HAVE_SSE41 + #define npyv_select_u8(MASK, A, B) _mm_blendv_epi8(B, A, MASK) + #define npyv_select_f32(MASK, A, B) _mm_blendv_ps(B, A, _mm_castsi128_ps(MASK)) + #define npyv_select_f64(MASK, A, B) _mm_blendv_pd(B, A, _mm_castsi128_pd(MASK)) +#else + NPY_FINLINE __m128i npyv_select_u8(__m128i mask, __m128i a, __m128i b) + { return _mm_xor_si128(b, _mm_and_si128(_mm_xor_si128(b, a), mask)); } + NPY_FINLINE __m128 npyv_select_f32(__m128i mask, __m128 a, __m128 b) + { return _mm_xor_ps(b, _mm_and_ps(_mm_xor_ps(b, a), _mm_castsi128_ps(mask))); } + NPY_FINLINE __m128d npyv_select_f64(__m128i mask, __m128d a, __m128d b) + { return _mm_xor_pd(b, _mm_and_pd(_mm_xor_pd(b, a), _mm_castsi128_pd(mask))); } +#endif +#define npyv_select_s8 npyv_select_u8 +#define npyv_select_u16 npyv_select_u8 +#define npyv_select_s16 npyv_select_u8 +#define npyv_select_u32 npyv_select_u8 +#define npyv_select_s32 npyv_select_u8 +#define npyv_select_u64 npyv_select_u8 +#define npyv_select_s64 npyv_select_u8 + +// Reinterpret +#define npyv_reinterpret_u8_u8(X) X +#define npyv_reinterpret_u8_s8(X) X +#define npyv_reinterpret_u8_u16(X) X +#define npyv_reinterpret_u8_s16(X) X +#define npyv_reinterpret_u8_u32(X) X +#define npyv_reinterpret_u8_s32(X) X +#define npyv_reinterpret_u8_u64(X) X +#define npyv_reinterpret_u8_s64(X) X +#define npyv_reinterpret_u8_f32 _mm_castps_si128 +#define npyv_reinterpret_u8_f64 _mm_castpd_si128 + +#define npyv_reinterpret_s8_s8(X) X +#define npyv_reinterpret_s8_u8(X) X +#define npyv_reinterpret_s8_u16(X) X +#define npyv_reinterpret_s8_s16(X) X +#define npyv_reinterpret_s8_u32(X) X +#define npyv_reinterpret_s8_s32(X) X +#define npyv_reinterpret_s8_u64(X) X +#define npyv_reinterpret_s8_s64(X) X +#define npyv_reinterpret_s8_f32 _mm_castps_si128 +#define npyv_reinterpret_s8_f64 _mm_castpd_si128 + +#define npyv_reinterpret_u16_u16(X) X +#define npyv_reinterpret_u16_u8(X) X +#define npyv_reinterpret_u16_s8(X) X +#define npyv_reinterpret_u16_s16(X) X +#define npyv_reinterpret_u16_u32(X) X +#define npyv_reinterpret_u16_s32(X) X +#define npyv_reinterpret_u16_u64(X) X +#define npyv_reinterpret_u16_s64(X) X +#define npyv_reinterpret_u16_f32 _mm_castps_si128 +#define npyv_reinterpret_u16_f64 _mm_castpd_si128 + +#define npyv_reinterpret_s16_s16(X) X +#define npyv_reinterpret_s16_u8(X) X +#define npyv_reinterpret_s16_s8(X) X +#define npyv_reinterpret_s16_u16(X) X +#define npyv_reinterpret_s16_u32(X) X +#define npyv_reinterpret_s16_s32(X) X +#define npyv_reinterpret_s16_u64(X) X +#define npyv_reinterpret_s16_s64(X) X +#define npyv_reinterpret_s16_f32 _mm_castps_si128 +#define npyv_reinterpret_s16_f64 _mm_castpd_si128 + +#define npyv_reinterpret_u32_u32(X) X +#define npyv_reinterpret_u32_u8(X) X +#define npyv_reinterpret_u32_s8(X) X +#define npyv_reinterpret_u32_u16(X) X +#define npyv_reinterpret_u32_s16(X) X +#define npyv_reinterpret_u32_s32(X) X +#define npyv_reinterpret_u32_u64(X) X +#define npyv_reinterpret_u32_s64(X) X +#define npyv_reinterpret_u32_f32 _mm_castps_si128 +#define npyv_reinterpret_u32_f64 _mm_castpd_si128 + +#define npyv_reinterpret_s32_s32(X) X +#define npyv_reinterpret_s32_u8(X) X +#define npyv_reinterpret_s32_s8(X) X +#define npyv_reinterpret_s32_u16(X) X +#define npyv_reinterpret_s32_s16(X) X +#define npyv_reinterpret_s32_u32(X) X +#define npyv_reinterpret_s32_u64(X) X +#define npyv_reinterpret_s32_s64(X) X +#define npyv_reinterpret_s32_f32 _mm_castps_si128 +#define npyv_reinterpret_s32_f64 _mm_castpd_si128 + +#define npyv_reinterpret_u64_u64(X) X +#define npyv_reinterpret_u64_u8(X) X +#define npyv_reinterpret_u64_s8(X) X +#define npyv_reinterpret_u64_u16(X) X +#define npyv_reinterpret_u64_s16(X) X +#define npyv_reinterpret_u64_u32(X) X +#define npyv_reinterpret_u64_s32(X) X +#define npyv_reinterpret_u64_s64(X) X +#define npyv_reinterpret_u64_f32 _mm_castps_si128 +#define npyv_reinterpret_u64_f64 _mm_castpd_si128 + +#define npyv_reinterpret_s64_s64(X) X +#define npyv_reinterpret_s64_u8(X) X +#define npyv_reinterpret_s64_s8(X) X +#define npyv_reinterpret_s64_u16(X) X +#define npyv_reinterpret_s64_s16(X) X +#define npyv_reinterpret_s64_u32(X) X +#define npyv_reinterpret_s64_s32(X) X +#define npyv_reinterpret_s64_u64(X) X +#define npyv_reinterpret_s64_f32 _mm_castps_si128 +#define npyv_reinterpret_s64_f64 _mm_castpd_si128 + +#define npyv_reinterpret_f32_f32(X) X +#define npyv_reinterpret_f32_u8 _mm_castsi128_ps +#define npyv_reinterpret_f32_s8 _mm_castsi128_ps +#define npyv_reinterpret_f32_u16 _mm_castsi128_ps +#define npyv_reinterpret_f32_s16 _mm_castsi128_ps +#define npyv_reinterpret_f32_u32 _mm_castsi128_ps +#define npyv_reinterpret_f32_s32 _mm_castsi128_ps +#define npyv_reinterpret_f32_u64 _mm_castsi128_ps +#define npyv_reinterpret_f32_s64 _mm_castsi128_ps +#define npyv_reinterpret_f32_f64 _mm_castpd_ps + +#define npyv_reinterpret_f64_f64(X) X +#define npyv_reinterpret_f64_u8 _mm_castsi128_pd +#define npyv_reinterpret_f64_s8 _mm_castsi128_pd +#define npyv_reinterpret_f64_u16 _mm_castsi128_pd +#define npyv_reinterpret_f64_s16 _mm_castsi128_pd +#define npyv_reinterpret_f64_u32 _mm_castsi128_pd +#define npyv_reinterpret_f64_s32 _mm_castsi128_pd +#define npyv_reinterpret_f64_u64 _mm_castsi128_pd +#define npyv_reinterpret_f64_s64 _mm_castsi128_pd +#define npyv_reinterpret_f64_f32 _mm_castps_pd + +// Only required by AVX2/AVX512 +#define npyv_cleanup() ((void)0) + +#endif // _NPY_SIMD_SSE_MISC_H diff --git a/numpy/core/src/common/simd/sse/operators.h b/numpy/core/src/common/simd/sse/operators.h new file mode 100644 index 000000000..6e32ca4fd --- /dev/null +++ b/numpy/core/src/common/simd/sse/operators.h @@ -0,0 +1,258 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_SSE_OPERATORS_H +#define _NPY_SIMD_SSE_OPERATORS_H + +/*************************** + * Shifting + ***************************/ + +// left +#define npyv_shl_u16(A, C) _mm_sll_epi16(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_s16(A, C) _mm_sll_epi16(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_u32(A, C) _mm_sll_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_s32(A, C) _mm_sll_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_u64(A, C) _mm_sll_epi64(A, _mm_cvtsi32_si128(C)) +#define npyv_shl_s64(A, C) _mm_sll_epi64(A, _mm_cvtsi32_si128(C)) + +// left by an immediate constant +#define npyv_shli_u16 _mm_slli_epi16 +#define npyv_shli_s16 _mm_slli_epi16 +#define npyv_shli_u32 _mm_slli_epi32 +#define npyv_shli_s32 _mm_slli_epi32 +#define npyv_shli_u64 _mm_slli_epi64 +#define npyv_shli_s64 _mm_slli_epi64 + +// right +#define npyv_shr_u16(A, C) _mm_srl_epi16(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_s16(A, C) _mm_sra_epi16(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_u32(A, C) _mm_srl_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_s32(A, C) _mm_sra_epi32(A, _mm_cvtsi32_si128(C)) +#define npyv_shr_u64(A, C) _mm_srl_epi64(A, _mm_cvtsi32_si128(C)) +NPY_FINLINE __m128i npyv_shr_s64(__m128i a, int c) +{ + const __m128i sbit = npyv_setall_s64(0x8000000000000000); + const __m128i cv = _mm_cvtsi32_si128(c); + __m128i r = _mm_srl_epi64(_mm_add_epi64(a, sbit), cv); + return _mm_sub_epi64(r, _mm_srl_epi64(sbit, cv)); +} + +// Right by an immediate constant +#define npyv_shri_u16 _mm_srli_epi16 +#define npyv_shri_s16 _mm_srai_epi16 +#define npyv_shri_u32 _mm_srli_epi32 +#define npyv_shri_s32 _mm_srai_epi32 +#define npyv_shri_u64 _mm_srli_epi64 +#define npyv_shri_s64 npyv_shr_s64 + +/*************************** + * Logical + ***************************/ + +// AND +#define npyv_and_u8 _mm_and_si128 +#define npyv_and_s8 _mm_and_si128 +#define npyv_and_u16 _mm_and_si128 +#define npyv_and_s16 _mm_and_si128 +#define npyv_and_u32 _mm_and_si128 +#define npyv_and_s32 _mm_and_si128 +#define npyv_and_u64 _mm_and_si128 +#define npyv_and_s64 _mm_and_si128 +#define npyv_and_f32 _mm_and_ps +#define npyv_and_f64 _mm_and_pd + +// OR +#define npyv_or_u8 _mm_or_si128 +#define npyv_or_s8 _mm_or_si128 +#define npyv_or_u16 _mm_or_si128 +#define npyv_or_s16 _mm_or_si128 +#define npyv_or_u32 _mm_or_si128 +#define npyv_or_s32 _mm_or_si128 +#define npyv_or_u64 _mm_or_si128 +#define npyv_or_s64 _mm_or_si128 +#define npyv_or_f32 _mm_or_ps +#define npyv_or_f64 _mm_or_pd + +// XOR +#define npyv_xor_u8 _mm_xor_si128 +#define npyv_xor_s8 _mm_xor_si128 +#define npyv_xor_u16 _mm_xor_si128 +#define npyv_xor_s16 _mm_xor_si128 +#define npyv_xor_u32 _mm_xor_si128 +#define npyv_xor_s32 _mm_xor_si128 +#define npyv_xor_u64 _mm_xor_si128 +#define npyv_xor_s64 _mm_xor_si128 +#define npyv_xor_f32 _mm_xor_ps +#define npyv_xor_f64 _mm_xor_pd + +// NOT +#define npyv_not_u8(A) _mm_xor_si128(A, _mm_set1_epi32(-1)) +#define npyv_not_s8 npyv_not_u8 +#define npyv_not_u16 npyv_not_u8 +#define npyv_not_s16 npyv_not_u8 +#define npyv_not_u32 npyv_not_u8 +#define npyv_not_s32 npyv_not_u8 +#define npyv_not_u64 npyv_not_u8 +#define npyv_not_s64 npyv_not_u8 +#define npyv_not_f32(A) _mm_xor_ps(A, _mm_castsi128_ps(_mm_set1_epi32(-1))) +#define npyv_not_f64(A) _mm_xor_pd(A, _mm_castsi128_pd(_mm_set1_epi32(-1))) + +/*************************** + * Comparison + ***************************/ + +// Int Equal +#define npyv_cmpeq_u8 _mm_cmpeq_epi8 +#define npyv_cmpeq_s8 _mm_cmpeq_epi8 +#define npyv_cmpeq_u16 _mm_cmpeq_epi16 +#define npyv_cmpeq_s16 _mm_cmpeq_epi16 +#define npyv_cmpeq_u32 _mm_cmpeq_epi32 +#define npyv_cmpeq_s32 _mm_cmpeq_epi32 +#define npyv_cmpeq_s64 npyv_cmpeq_u64 + +#ifdef NPY_HAVE_SSE41 + #define npyv_cmpeq_u64 _mm_cmpeq_epi64 +#else + NPY_FINLINE __m128i npyv_cmpeq_u64(__m128i a, __m128i b) + { + __m128i cmpeq = _mm_cmpeq_epi32(a, b); + __m128i cmpeq_h = _mm_srli_epi64(cmpeq, 32); + __m128i test = _mm_and_si128(cmpeq, cmpeq_h); + return _mm_shuffle_epi32(test, _MM_SHUFFLE(2, 2, 0, 0)); + } +#endif + +// Int Not Equal +#ifdef NPY_HAVE_XOP + #define npyv_cmpneq_u8 _mm_comneq_epi8 + #define npyv_cmpneq_u16 _mm_comneq_epi16 + #define npyv_cmpneq_u32 _mm_comneq_epi32 + #define npyv_cmpneq_u64 _mm_comneq_epi64 +#else + #define npyv_cmpneq_u8(A, B) npyv_not_u8(npyv_cmpeq_u8(A, B)) + #define npyv_cmpneq_u16(A, B) npyv_not_u16(npyv_cmpeq_u16(A, B)) + #define npyv_cmpneq_u32(A, B) npyv_not_u32(npyv_cmpeq_u32(A, B)) + #define npyv_cmpneq_u64(A, B) npyv_not_u64(npyv_cmpeq_u64(A, B)) +#endif +#define npyv_cmpneq_s8 npyv_cmpneq_u8 +#define npyv_cmpneq_s16 npyv_cmpneq_u16 +#define npyv_cmpneq_s32 npyv_cmpneq_u32 +#define npyv_cmpneq_s64 npyv_cmpneq_u64 + +// signed greater than +#define npyv_cmpgt_s8 _mm_cmpgt_epi8 +#define npyv_cmpgt_s16 _mm_cmpgt_epi16 +#define npyv_cmpgt_s32 _mm_cmpgt_epi32 + +#ifdef NPY_HAVE_SSE42 + #define npyv_cmpgt_s64 _mm_cmpgt_epi64 +#else + NPY_FINLINE __m128i npyv_cmpgt_s64(__m128i a, __m128i b) + { + __m128i sub = _mm_sub_epi64(b, a); + __m128i nsame_sbit = _mm_xor_si128(a, b); + // nsame_sbit ? b : sub + __m128i test = _mm_xor_si128(sub, _mm_and_si128(_mm_xor_si128(sub, b), nsame_sbit)); + __m128i extend_sbit = _mm_shuffle_epi32(_mm_srai_epi32(test, 31), _MM_SHUFFLE(3, 3, 1, 1)); + return extend_sbit; + } +#endif + +// signed greater than or equal +#ifdef NPY_HAVE_XOP + #define npyv_cmpge_s8 _mm_comge_epi8 + #define npyv_cmpge_s16 _mm_comge_epi16 + #define npyv_cmpge_s32 _mm_comge_epi32 + #define npyv_cmpge_s64 _mm_comge_epi64 +#else + #define npyv_cmpge_s8(A, B) npyv_not_s8(_mm_cmpgt_epi8(B, A)) + #define npyv_cmpge_s16(A, B) npyv_not_s16(_mm_cmpgt_epi16(B, A)) + #define npyv_cmpge_s32(A, B) npyv_not_s32(_mm_cmpgt_epi32(B, A)) + #define npyv_cmpge_s64(A, B) npyv_not_s64(npyv_cmpgt_s64(B, A)) +#endif + +// unsigned greater than +#ifdef NPY_HAVE_XOP + #define npyv_cmpgt_u8 _mm_comgt_epu8 + #define npyv_cmpgt_u16 _mm_comgt_epu16 + #define npyv_cmpgt_u32 _mm_comgt_epu32 + #define npyv_cmpgt_u64 _mm_comgt_epu64 +#else + #define NPYV_IMPL_SSE_UNSIGNED_GT(LEN, SIGN) \ + NPY_FINLINE __m128i npyv_cmpgt_u##LEN(__m128i a, __m128i b) \ + { \ + const __m128i sbit = _mm_set1_epi32(SIGN); \ + return _mm_cmpgt_epi##LEN( \ + _mm_xor_si128(a, sbit), _mm_xor_si128(b, sbit) \ + ); \ + } + + NPYV_IMPL_SSE_UNSIGNED_GT(8, 0x80808080) + NPYV_IMPL_SSE_UNSIGNED_GT(16, 0x80008000) + NPYV_IMPL_SSE_UNSIGNED_GT(32, 0x80000000) + + NPY_FINLINE __m128i npyv_cmpgt_u64(__m128i a, __m128i b) + { + const __m128i sbit = npyv_setall_s64(0x8000000000000000); + return npyv_cmpgt_s64(_mm_xor_si128(a, sbit), _mm_xor_si128(b, sbit)); + } +#endif + +// unsigned greater than or equal +#ifdef NPY_HAVE_XOP + #define npyv_cmpge_u8 _mm_comge_epu8 + #define npyv_cmpge_u16 _mm_comge_epu16 + #define npyv_cmpge_u32 _mm_comge_epu32 + #define npyv_cmpge_u64 _mm_comge_epu64 +#else + NPY_FINLINE __m128i npyv_cmpge_u8(__m128i a, __m128i b) + { return _mm_cmpeq_epi8(a, _mm_max_epu8(a, b)); } + #ifdef NPY_HAVE_SSE41 + NPY_FINLINE __m128i npyv_cmpge_u16(__m128i a, __m128i b) + { return _mm_cmpeq_epi16(a, _mm_max_epu16(a, b)); } + NPY_FINLINE __m128i npyv_cmpge_u32(__m128i a, __m128i b) + { return _mm_cmpeq_epi32(a, _mm_max_epu32(a, b)); } + #else + #define npyv_cmpge_u16(A, B) _mm_cmpeq_epi16(_mm_subs_epu16(B, A), _mm_setzero_si128()) + #define npyv_cmpge_u32(A, B) npyv_not_u32(npyv_cmpgt_u32(B, A)) + #endif + #define npyv_cmpge_u64(A, B) npyv_not_u64(npyv_cmpgt_u64(B, A)) +#endif + +// less than +#define npyv_cmplt_u8(A, B) npyv_cmpgt_u8(B, A) +#define npyv_cmplt_s8(A, B) npyv_cmpgt_s8(B, A) +#define npyv_cmplt_u16(A, B) npyv_cmpgt_u16(B, A) +#define npyv_cmplt_s16(A, B) npyv_cmpgt_s16(B, A) +#define npyv_cmplt_u32(A, B) npyv_cmpgt_u32(B, A) +#define npyv_cmplt_s32(A, B) npyv_cmpgt_s32(B, A) +#define npyv_cmplt_u64(A, B) npyv_cmpgt_u64(B, A) +#define npyv_cmplt_s64(A, B) npyv_cmpgt_s64(B, A) + +// less than or equal +#define npyv_cmple_u8(A, B) npyv_cmpge_u8(B, A) +#define npyv_cmple_s8(A, B) npyv_cmpge_s8(B, A) +#define npyv_cmple_u16(A, B) npyv_cmpge_u16(B, A) +#define npyv_cmple_s16(A, B) npyv_cmpge_s16(B, A) +#define npyv_cmple_u32(A, B) npyv_cmpge_u32(B, A) +#define npyv_cmple_s32(A, B) npyv_cmpge_s32(B, A) +#define npyv_cmple_u64(A, B) npyv_cmpge_u64(B, A) +#define npyv_cmple_s64(A, B) npyv_cmpge_s64(B, A) + +// precision comparison +#define npyv_cmpeq_f32(a, b) _mm_castps_si128(_mm_cmpeq_ps(a, b)) +#define npyv_cmpeq_f64(a, b) _mm_castpd_si128(_mm_cmpeq_pd(a, b)) +#define npyv_cmpneq_f32(a, b) _mm_castps_si128(_mm_cmpneq_ps(a, b)) +#define npyv_cmpneq_f64(a, b) _mm_castpd_si128(_mm_cmpneq_pd(a, b)) +#define npyv_cmplt_f32(a, b) _mm_castps_si128(_mm_cmplt_ps(a, b)) +#define npyv_cmplt_f64(a, b) _mm_castpd_si128(_mm_cmplt_pd(a, b)) +#define npyv_cmple_f32(a, b) _mm_castps_si128(_mm_cmple_ps(a, b)) +#define npyv_cmple_f64(a, b) _mm_castpd_si128(_mm_cmple_pd(a, b)) +#define npyv_cmpgt_f32(a, b) _mm_castps_si128(_mm_cmpgt_ps(a, b)) +#define npyv_cmpgt_f64(a, b) _mm_castpd_si128(_mm_cmpgt_pd(a, b)) +#define npyv_cmpge_f32(a, b) _mm_castps_si128(_mm_cmpge_ps(a, b)) +#define npyv_cmpge_f64(a, b) _mm_castpd_si128(_mm_cmpge_pd(a, b)) + +#endif // _NPY_SIMD_SSE_OPERATORS_H diff --git a/numpy/core/src/common/simd/sse/reorder.h b/numpy/core/src/common/simd/sse/reorder.h new file mode 100644 index 000000000..3f68b4ad7 --- /dev/null +++ b/numpy/core/src/common/simd/sse/reorder.h @@ -0,0 +1,84 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_SSE_REORDER_H +#define _NPY_SIMD_SSE_REORDER_H + +// combine lower part of two vectors +#define npyv_combinel_u8 _mm_unpacklo_epi64 +#define npyv_combinel_s8 _mm_unpacklo_epi64 +#define npyv_combinel_u16 _mm_unpacklo_epi64 +#define npyv_combinel_s16 _mm_unpacklo_epi64 +#define npyv_combinel_u32 _mm_unpacklo_epi64 +#define npyv_combinel_s32 _mm_unpacklo_epi64 +#define npyv_combinel_u64 _mm_unpacklo_epi64 +#define npyv_combinel_s64 _mm_unpacklo_epi64 +#define npyv_combinel_f32(A, B) _mm_castsi128_ps(_mm_unpacklo_epi64(_mm_castps_si128(A), _mm_castps_si128(B))) +#define npyv_combinel_f64 _mm_unpacklo_pd + +// combine higher part of two vectors +#define npyv_combineh_u8 _mm_unpackhi_epi64 +#define npyv_combineh_s8 _mm_unpackhi_epi64 +#define npyv_combineh_u16 _mm_unpackhi_epi64 +#define npyv_combineh_s16 _mm_unpackhi_epi64 +#define npyv_combineh_u32 _mm_unpackhi_epi64 +#define npyv_combineh_s32 _mm_unpackhi_epi64 +#define npyv_combineh_u64 _mm_unpackhi_epi64 +#define npyv_combineh_s64 _mm_unpackhi_epi64 +#define npyv_combineh_f32(A, B) _mm_castsi128_ps(_mm_unpackhi_epi64(_mm_castps_si128(A), _mm_castps_si128(B))) +#define npyv_combineh_f64 _mm_unpackhi_pd + +// combine two vectors from lower and higher parts of two other vectors +NPY_FINLINE npyv_m128ix2 npyv__combine(__m128i a, __m128i b) +{ + npyv_m128ix2 r; + r.val[0] = npyv_combinel_u8(a, b); + r.val[1] = npyv_combineh_u8(a, b); + return r; +} +NPY_FINLINE npyv_f32x2 npyv_combine_f32(__m128 a, __m128 b) +{ + npyv_f32x2 r; + r.val[0] = npyv_combinel_f32(a, b); + r.val[1] = npyv_combineh_f32(a, b); + return r; +} +NPY_FINLINE npyv_f64x2 npyv_combine_f64(__m128d a, __m128d b) +{ + npyv_f64x2 r; + r.val[0] = npyv_combinel_f64(a, b); + r.val[1] = npyv_combineh_f64(a, b); + return r; +} +#define npyv_combine_u8 npyv__combine +#define npyv_combine_s8 npyv__combine +#define npyv_combine_u16 npyv__combine +#define npyv_combine_s16 npyv__combine +#define npyv_combine_u32 npyv__combine +#define npyv_combine_s32 npyv__combine +#define npyv_combine_u64 npyv__combine +#define npyv_combine_s64 npyv__combine + +// interleave two vectors +#define NPYV_IMPL_SSE_ZIP(T_VEC, SFX, INTR_SFX) \ + NPY_FINLINE T_VEC##x2 npyv_zip_##SFX(T_VEC a, T_VEC b) \ + { \ + T_VEC##x2 r; \ + r.val[0] = _mm_unpacklo_##INTR_SFX(a, b); \ + r.val[1] = _mm_unpackhi_##INTR_SFX(a, b); \ + return r; \ + } + +NPYV_IMPL_SSE_ZIP(npyv_u8, u8, epi8) +NPYV_IMPL_SSE_ZIP(npyv_s8, s8, epi8) +NPYV_IMPL_SSE_ZIP(npyv_u16, u16, epi16) +NPYV_IMPL_SSE_ZIP(npyv_s16, s16, epi16) +NPYV_IMPL_SSE_ZIP(npyv_u32, u32, epi32) +NPYV_IMPL_SSE_ZIP(npyv_s32, s32, epi32) +NPYV_IMPL_SSE_ZIP(npyv_u64, u64, epi64) +NPYV_IMPL_SSE_ZIP(npyv_s64, s64, epi64) +NPYV_IMPL_SSE_ZIP(npyv_f32, f32, ps) +NPYV_IMPL_SSE_ZIP(npyv_f64, f64, pd) + +#endif // _NPY_SIMD_SSE_REORDER_H diff --git a/numpy/core/src/common/simd/sse/sse.h b/numpy/core/src/common/simd/sse/sse.h new file mode 100644 index 000000000..364b4baf1 --- /dev/null +++ b/numpy/core/src/common/simd/sse/sse.h @@ -0,0 +1,66 @@ +#ifndef _NPY_SIMD_H_ + #error "Not a standalone header" +#endif + +#define NPY_SIMD 128 +#define NPY_SIMD_WIDTH 16 +#define NPY_SIMD_F64 1 + +typedef __m128i npyv_u8; +typedef __m128i npyv_s8; +typedef __m128i npyv_u16; +typedef __m128i npyv_s16; +typedef __m128i npyv_u32; +typedef __m128i npyv_s32; +typedef __m128i npyv_u64; +typedef __m128i npyv_s64; +typedef __m128 npyv_f32; +typedef __m128d npyv_f64; + +typedef __m128i npyv_b8; +typedef __m128i npyv_b16; +typedef __m128i npyv_b32; +typedef __m128i npyv_b64; + +typedef struct { __m128i val[2]; } npyv_m128ix2; +typedef npyv_m128ix2 npyv_u8x2; +typedef npyv_m128ix2 npyv_s8x2; +typedef npyv_m128ix2 npyv_u16x2; +typedef npyv_m128ix2 npyv_s16x2; +typedef npyv_m128ix2 npyv_u32x2; +typedef npyv_m128ix2 npyv_s32x2; +typedef npyv_m128ix2 npyv_u64x2; +typedef npyv_m128ix2 npyv_s64x2; + +typedef struct { __m128i val[3]; } npyv_m128ix3; +typedef npyv_m128ix3 npyv_u8x3; +typedef npyv_m128ix3 npyv_s8x3; +typedef npyv_m128ix3 npyv_u16x3; +typedef npyv_m128ix3 npyv_s16x3; +typedef npyv_m128ix3 npyv_u32x3; +typedef npyv_m128ix3 npyv_s32x3; +typedef npyv_m128ix3 npyv_u64x3; +typedef npyv_m128ix3 npyv_s64x3; + +typedef struct { __m128 val[2]; } npyv_f32x2; +typedef struct { __m128d val[2]; } npyv_f64x2; +typedef struct { __m128 val[3]; } npyv_f32x3; +typedef struct { __m128d val[3]; } npyv_f64x3; + +#define npyv_nlanes_u8 16 +#define npyv_nlanes_s8 16 +#define npyv_nlanes_u16 8 +#define npyv_nlanes_s16 8 +#define npyv_nlanes_u32 4 +#define npyv_nlanes_s32 4 +#define npyv_nlanes_u64 2 +#define npyv_nlanes_s64 2 +#define npyv_nlanes_f32 4 +#define npyv_nlanes_f64 2 + +#include "memory.h" +#include "misc.h" +#include "reorder.h" +#include "operators.h" +#include "conversion.h" +#include "arithmetic.h" diff --git a/numpy/core/src/common/simd/vsx/arithmetic.h b/numpy/core/src/common/simd/vsx/arithmetic.h new file mode 100644 index 000000000..dd23b5b11 --- /dev/null +++ b/numpy/core/src/common/simd/vsx/arithmetic.h @@ -0,0 +1,103 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_VSX_ARITHMETIC_H +#define _NPY_SIMD_VSX_ARITHMETIC_H + +/*************************** + * Addition + ***************************/ +// non-saturated +#define npyv_add_u8 vec_add +#define npyv_add_s8 vec_add +#define npyv_add_u16 vec_add +#define npyv_add_s16 vec_add +#define npyv_add_u32 vec_add +#define npyv_add_s32 vec_add +#define npyv_add_u64 vec_add +#define npyv_add_s64 vec_add +#define npyv_add_f32 vec_add +#define npyv_add_f64 vec_add + +// saturated +#define npyv_adds_u8 vec_adds +#define npyv_adds_s8 vec_adds +#define npyv_adds_u16 vec_adds +#define npyv_adds_s16 vec_adds + +/*************************** + * Subtraction + ***************************/ +// non-saturated +#define npyv_sub_u8 vec_sub +#define npyv_sub_s8 vec_sub +#define npyv_sub_u16 vec_sub +#define npyv_sub_s16 vec_sub +#define npyv_sub_u32 vec_sub +#define npyv_sub_s32 vec_sub +#define npyv_sub_u64 vec_sub +#define npyv_sub_s64 vec_sub +#define npyv_sub_f32 vec_sub +#define npyv_sub_f64 vec_sub + +// saturated +#define npyv_subs_u8 vec_subs +#define npyv_subs_s8 vec_subs +#define npyv_subs_u16 vec_subs +#define npyv_subs_s16 vec_subs + +/*************************** + * Multiplication + ***************************/ +// non-saturated +// up to GCC 6 vec_mul only supports precisions and llong +#if defined(__GNUC__) && __GNUC__ < 7 + #define NPYV_IMPL_VSX_MUL(T_VEC, SFX, ...) \ + NPY_FINLINE T_VEC npyv_mul_##SFX(T_VEC a, T_VEC b) \ + { \ + const npyv_u8 ev_od = {__VA_ARGS__}; \ + return vec_perm( \ + (T_VEC)vec_mule(a, b), \ + (T_VEC)vec_mulo(a, b), ev_od \ + ); \ + } + + NPYV_IMPL_VSX_MUL(npyv_u8, u8, 0, 16, 2, 18, 4, 20, 6, 22, 8, 24, 10, 26, 12, 28, 14, 30) + NPYV_IMPL_VSX_MUL(npyv_s8, s8, 0, 16, 2, 18, 4, 20, 6, 22, 8, 24, 10, 26, 12, 28, 14, 30) + NPYV_IMPL_VSX_MUL(npyv_u16, u16, 0, 1, 16, 17, 4, 5, 20, 21, 8, 9, 24, 25, 12, 13, 28, 29) + NPYV_IMPL_VSX_MUL(npyv_s16, s16, 0, 1, 16, 17, 4, 5, 20, 21, 8, 9, 24, 25, 12, 13, 28, 29) + + // vmuluwm can be used for unsigned or signed 32-bit integers + #define NPYV_IMPL_VSX_MUL_32(T_VEC, SFX) \ + NPY_FINLINE T_VEC npyv_mul_##SFX(T_VEC a, T_VEC b) \ + { \ + T_VEC ret; \ + __asm__ __volatile__( \ + "vmuluwm %0,%1,%2" : \ + "=v" (ret) : "v" (a), "v" (b) \ + ); \ + return ret; \ + } + + NPYV_IMPL_VSX_MUL_32(npyv_u32, u32) + NPYV_IMPL_VSX_MUL_32(npyv_s32, s32) + +#else + #define npyv_mul_u8 vec_mul + #define npyv_mul_s8 vec_mul + #define npyv_mul_u16 vec_mul + #define npyv_mul_s16 vec_mul + #define npyv_mul_u32 vec_mul + #define npyv_mul_s32 vec_mul +#endif +#define npyv_mul_f32 vec_mul +#define npyv_mul_f64 vec_mul + +/*************************** + * Division + ***************************/ +#define npyv_div_f32 vec_div +#define npyv_div_f64 vec_div + +#endif // _NPY_SIMD_VSX_ARITHMETIC_H diff --git a/numpy/core/src/common/simd/vsx/conversion.h b/numpy/core/src/common/simd/vsx/conversion.h new file mode 100644 index 000000000..6ed135990 --- /dev/null +++ b/numpy/core/src/common/simd/vsx/conversion.h @@ -0,0 +1,32 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_VSX_CVT_H +#define _NPY_SIMD_VSX_CVT_H + +// convert boolean vectors to integer vectors +#define npyv_cvt_u8_b8(BL) ((npyv_u8) BL) +#define npyv_cvt_s8_b8(BL) ((npyv_s8) BL) +#define npyv_cvt_u16_b16(BL) ((npyv_u16) BL) +#define npyv_cvt_s16_b16(BL) ((npyv_s16) BL) +#define npyv_cvt_u32_b32(BL) ((npyv_u32) BL) +#define npyv_cvt_s32_b32(BL) ((npyv_s32) BL) +#define npyv_cvt_u64_b64(BL) ((npyv_u64) BL) +#define npyv_cvt_s64_b64(BL) ((npyv_s64) BL) +#define npyv_cvt_f32_b32(BL) ((npyv_f32) BL) +#define npyv_cvt_f64_b64(BL) ((npyv_f64) BL) + +// convert integer vectors to boolean vectors +#define npyv_cvt_b8_u8(A) ((npyv_b8) A) +#define npyv_cvt_b8_s8(A) ((npyv_b8) A) +#define npyv_cvt_b16_u16(A) ((npyv_b16) A) +#define npyv_cvt_b16_s16(A) ((npyv_b16) A) +#define npyv_cvt_b32_u32(A) ((npyv_b32) A) +#define npyv_cvt_b32_s32(A) ((npyv_b32) A) +#define npyv_cvt_b64_u64(A) ((npyv_b64) A) +#define npyv_cvt_b64_s64(A) ((npyv_b64) A) +#define npyv_cvt_b32_f32(A) ((npyv_b32) A) +#define npyv_cvt_b64_f64(A) ((npyv_b64) A) + +#endif // _NPY_SIMD_VSX_CVT_H diff --git a/numpy/core/src/common/simd/vsx/memory.h b/numpy/core/src/common/simd/vsx/memory.h new file mode 100644 index 000000000..e0d908bf9 --- /dev/null +++ b/numpy/core/src/common/simd/vsx/memory.h @@ -0,0 +1,150 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_VSX_MEMORY_H +#define _NPY_SIMD_VSX_MEMORY_H +/**************************** + * load/store + ****************************/ +// TODO: test load by cast +#define VSX__CAST_lOAD 0 +#if VSX__CAST_lOAD + #define npyv__load(PTR, T_VEC) (*((T_VEC*)(PTR))) +#else + /** + * CLANG fails to load unaligned addresses via vec_xl, vec_xst + * so we failback to vec_vsx_ld, vec_vsx_st + */ + #if (defined(__GNUC__) && !defined(vec_xl)) || (defined(__clang__) && !defined(__IBMC__)) + #define npyv__load(PTR, T_VEC) vec_vsx_ld(0, PTR) + #else + #define npyv__load(PTR, T_VEC) vec_xl(0, PTR) + #endif +#endif +// unaligned load +#define npyv_load_u8(PTR) npyv__load(PTR, npyv_u8) +#define npyv_load_s8(PTR) npyv__load(PTR, npyv_s8) +#define npyv_load_u16(PTR) npyv__load(PTR, npyv_u16) +#define npyv_load_s16(PTR) npyv__load(PTR, npyv_s16) +#define npyv_load_u32(PTR) npyv__load(PTR, npyv_u32) +#define npyv_load_s32(PTR) npyv__load(PTR, npyv_s32) +#define npyv_load_f32(PTR) npyv__load(PTR, npyv_f32) +#define npyv_load_f64(PTR) npyv__load(PTR, npyv_f64) +#if VSX__CAST_lOAD + #define npyv_load_u64(PTR) npyv__load(PTR, npyv_u64) + #define npyv_load_s64(PTR) npyv__load(PTR, npyv_s64) +#else + #define npyv_load_u64(PTR) ((npyv_u64)npyv_load_u32((const unsigned int*)PTR)) + #define npyv_load_s64(PTR) ((npyv_s64)npyv_load_s32((const unsigned int*)PTR)) +#endif +// aligned load +#define npyv_loada_u8(PTR) vec_ld(0, PTR) +#define npyv_loada_s8 npyv_loada_u8 +#define npyv_loada_u16 npyv_loada_u8 +#define npyv_loada_s16 npyv_loada_u8 +#define npyv_loada_u32 npyv_loada_u8 +#define npyv_loada_s32 npyv_loada_u8 +#define npyv_loada_u64 npyv_load_u64 +#define npyv_loada_s64 npyv_load_s64 +#define npyv_loada_f32 npyv_loada_u8 +#define npyv_loada_f64 npyv_load_f64 +// stream load +#define npyv_loads_u8 npyv_loada_u8 +#define npyv_loads_s8 npyv_loada_s8 +#define npyv_loads_u16 npyv_loada_u16 +#define npyv_loads_s16 npyv_loada_s16 +#define npyv_loads_u32 npyv_loada_u32 +#define npyv_loads_s32 npyv_loada_s32 +#define npyv_loads_u64 npyv_loada_u64 +#define npyv_loads_s64 npyv_loada_s64 +#define npyv_loads_f32 npyv_loada_f32 +#define npyv_loads_f64 npyv_loada_f64 +// load lower part +// avoid aliasing rules +#ifdef __cplusplus + template<typename T_PTR> + NPY_FINLINE npy_uint64 *npyv__ptr2u64(T_PTR *ptr) + { return npy_uint64 *ptr64 = (npy_uint64*)ptr; return ptr; } +#else + NPY_FINLINE npy_uint64 *npyv__ptr2u64(void *ptr) + { npy_uint64 *ptr64 = ptr; return ptr64; } +#endif // __cplusplus +#if defined(__clang__) && !defined(__IBMC__) + // vec_promote doesn't support doubleword on clang + #define npyv_loadl_u64(PTR) npyv_setall_u64(*npyv__ptr2u64(PTR)) +#else + #define npyv_loadl_u64(PTR) vec_promote(*npyv__ptr2u64(PTR), 0) +#endif +#define npyv_loadl_u8(PTR) ((npyv_u8)npyv_loadl_u64(PTR)) +#define npyv_loadl_s8(PTR) ((npyv_s8)npyv_loadl_u64(PTR)) +#define npyv_loadl_u16(PTR) ((npyv_u16)npyv_loadl_u64(PTR)) +#define npyv_loadl_s16(PTR) ((npyv_s16)npyv_loadl_u64(PTR)) +#define npyv_loadl_u32(PTR) ((npyv_u32)npyv_loadl_u64(PTR)) +#define npyv_loadl_s32(PTR) ((npyv_s32)npyv_loadl_u64(PTR)) +#define npyv_loadl_s64(PTR) ((npyv_s64)npyv_loadl_u64(PTR)) +#define npyv_loadl_f32(PTR) ((npyv_f32)npyv_loadl_u64(PTR)) +#define npyv_loadl_f64(PTR) ((npyv_f64)npyv_loadl_u64(PTR)) +// unaligned store +#if (defined(__GNUC__) && !defined(vec_xl)) || (defined(__clang__) && !defined(__IBMC__)) + #define npyv_store_u8(PTR, VEC) vec_vsx_st(VEC, 0, PTR) +#else + #define npyv_store_u8(PTR, VEC) vec_xst(VEC, 0, PTR) +#endif +#define npyv_store_s8 npyv_store_u8 +#define npyv_store_u16 npyv_store_u8 +#define npyv_store_s16 npyv_store_u8 +#define npyv_store_u32 npyv_store_u8 +#define npyv_store_s32 npyv_store_u8 +#define npyv_store_u64(PTR, VEC) npyv_store_u8((unsigned int*)PTR, (npyv_u32)VEC) +#define npyv_store_s64(PTR, VEC) npyv_store_u8((unsigned int*)PTR, (npyv_u32)VEC) +#define npyv_store_f32 npyv_store_u8 +#define npyv_store_f64 npyv_store_u8 +// aligned store +#define npyv_storea_u8(PTR, VEC) vec_st(VEC, 0, PTR) +#define npyv_storea_s8 npyv_storea_u8 +#define npyv_storea_u16 npyv_storea_u8 +#define npyv_storea_s16 npyv_storea_u8 +#define npyv_storea_u32 npyv_storea_u8 +#define npyv_storea_s32 npyv_storea_u8 +#define npyv_storea_u64 npyv_store_u64 +#define npyv_storea_s64 npyv_store_s64 +#define npyv_storea_f32 npyv_storea_u8 +#define npyv_storea_f64 npyv_store_f64 +// stream store +#define npyv_stores_u8 npyv_storea_u8 +#define npyv_stores_s8 npyv_storea_s8 +#define npyv_stores_u16 npyv_storea_u16 +#define npyv_stores_s16 npyv_storea_s16 +#define npyv_stores_u32 npyv_storea_u32 +#define npyv_stores_s32 npyv_storea_s32 +#define npyv_stores_u64 npyv_storea_u64 +#define npyv_stores_s64 npyv_storea_s64 +#define npyv_stores_f32 npyv_storea_f32 +#define npyv_stores_f64 npyv_storea_f64 +// store lower part +#define npyv_storel_u8(PTR, VEC) \ + *npyv__ptr2u64(PTR) = vec_extract(((npyv_u64)VEC), 0) +#define npyv_storel_s8 npyv_storel_u8 +#define npyv_storel_u16 npyv_storel_u8 +#define npyv_storel_s16 npyv_storel_u8 +#define npyv_storel_u32 npyv_storel_u8 +#define npyv_storel_s32 npyv_storel_u8 +#define npyv_storel_s64 npyv_storel_u8 +#define npyv_storel_u64 npyv_storel_u8 +#define npyv_storel_f32 npyv_storel_u8 +#define npyv_storel_f64 npyv_storel_u8 +// store higher part +#define npyv_storeh_u8(PTR, VEC) \ + *npyv__ptr2u64(PTR) = vec_extract(((npyv_u64)VEC), 1) +#define npyv_storeh_s8 npyv_storeh_u8 +#define npyv_storeh_u16 npyv_storeh_u8 +#define npyv_storeh_s16 npyv_storeh_u8 +#define npyv_storeh_u32 npyv_storeh_u8 +#define npyv_storeh_s32 npyv_storeh_u8 +#define npyv_storeh_s64 npyv_storeh_u8 +#define npyv_storeh_u64 npyv_storeh_u8 +#define npyv_storeh_f32 npyv_storeh_u8 +#define npyv_storeh_f64 npyv_storeh_u8 + +#endif // _NPY_SIMD_VSX_MEMORY_H diff --git a/numpy/core/src/common/simd/vsx/misc.h b/numpy/core/src/common/simd/vsx/misc.h new file mode 100644 index 000000000..f7a0cdd5c --- /dev/null +++ b/numpy/core/src/common/simd/vsx/misc.h @@ -0,0 +1,190 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_VSX_MISC_H +#define _NPY_SIMD_VSX_MISC_H + +// vector with zero lanes +#define npyv_zero_u8() ((npyv_u8) npyv_setall_s32(0)) +#define npyv_zero_s8() ((npyv_s8) npyv_setall_s32(0)) +#define npyv_zero_u16() ((npyv_u16) npyv_setall_s32(0)) +#define npyv_zero_s16() ((npyv_s16) npyv_setall_s32(0)) +#define npyv_zero_u32() npyv_setall_u32(0) +#define npyv_zero_s32() npyv_setall_s32(0) +#define npyv_zero_u64() ((npyv_u64) npyv_setall_s32(0)) +#define npyv_zero_s64() ((npyv_s64) npyv_setall_s32(0)) +#define npyv_zero_f32() npyv_setall_f32(0.0f) +#define npyv_zero_f64() npyv_setall_f64(0.0) + +// vector with a specific value set to all lanes +// the safest way to generate vsplti* and vsplt* instructions +#define NPYV_IMPL_VSX_SPLTB(T_VEC, V) ((T_VEC){V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V}) +#define NPYV_IMPL_VSX_SPLTH(T_VEC, V) ((T_VEC){V, V, V, V, V, V, V, V}) +#define NPYV_IMPL_VSX_SPLTW(T_VEC, V) ((T_VEC){V, V, V, V}) +#define NPYV_IMPL_VSX_SPLTD(T_VEC, V) ((T_VEC){V, V}) + +#define npyv_setall_u8(VAL) NPYV_IMPL_VSX_SPLTB(npyv_u8, (unsigned char)VAL) +#define npyv_setall_s8(VAL) NPYV_IMPL_VSX_SPLTB(npyv_s8, (signed char)VAL) +#define npyv_setall_u16(VAL) NPYV_IMPL_VSX_SPLTH(npyv_u16, (unsigned short)VAL) +#define npyv_setall_s16(VAL) NPYV_IMPL_VSX_SPLTH(npyv_s16, (short)VAL) +#define npyv_setall_u32(VAL) NPYV_IMPL_VSX_SPLTW(npyv_u32, (unsigned int)VAL) +#define npyv_setall_s32(VAL) NPYV_IMPL_VSX_SPLTW(npyv_s32, (int)VAL) +#define npyv_setall_f32(VAL) NPYV_IMPL_VSX_SPLTW(npyv_f32, VAL) +#define npyv_setall_u64(VAL) NPYV_IMPL_VSX_SPLTD(npyv_u64, (npy_uint64)VAL) +#define npyv_setall_s64(VAL) NPYV_IMPL_VSX_SPLTD(npyv_s64, (npy_int64)VAL) +#define npyv_setall_f64(VAL) NPYV_IMPL_VSX_SPLTD(npyv_f64, VAL) + +// vector with specific values set to each lane and +// set a specific value to all remained lanes +#define npyv_setf_u8(FILL, ...) ((npyv_u8){NPYV__SET_FILL_16(char, FILL, __VA_ARGS__)}) +#define npyv_setf_s8(FILL, ...) ((npyv_s8){NPYV__SET_FILL_16(char, FILL, __VA_ARGS__)}) +#define npyv_setf_u16(FILL, ...) ((npyv_u16){NPYV__SET_FILL_8(short, FILL, __VA_ARGS__)}) +#define npyv_setf_s16(FILL, ...) ((npyv_s16){NPYV__SET_FILL_8(short, FILL, __VA_ARGS__)}) +#define npyv_setf_u32(FILL, ...) ((npyv_u32){NPYV__SET_FILL_4(int, FILL, __VA_ARGS__)}) +#define npyv_setf_s32(FILL, ...) ((npyv_s32){NPYV__SET_FILL_4(int, FILL, __VA_ARGS__)}) +#define npyv_setf_u64(FILL, ...) ((npyv_u64){NPYV__SET_FILL_2(npy_int64, FILL, __VA_ARGS__)}) +#define npyv_setf_s64(FILL, ...) ((npyv_s64){NPYV__SET_FILL_2(npy_int64, FILL, __VA_ARGS__)}) +#define npyv_setf_f32(FILL, ...) ((npyv_f32){NPYV__SET_FILL_4(float, FILL, __VA_ARGS__)}) +#define npyv_setf_f64(FILL, ...) ((npyv_f64){NPYV__SET_FILL_2(double, FILL, __VA_ARGS__)}) + +// vector with specific values set to each lane and +// set zero to all remained lanes +#define npyv_set_u8(...) npyv_setf_u8(0, __VA_ARGS__) +#define npyv_set_s8(...) npyv_setf_s8(0, __VA_ARGS__) +#define npyv_set_u16(...) npyv_setf_u16(0, __VA_ARGS__) +#define npyv_set_s16(...) npyv_setf_s16(0, __VA_ARGS__) +#define npyv_set_u32(...) npyv_setf_u32(0, __VA_ARGS__) +#define npyv_set_s32(...) npyv_setf_s32(0, __VA_ARGS__) +#define npyv_set_u64(...) npyv_setf_u64(0, __VA_ARGS__) +#define npyv_set_s64(...) npyv_setf_s64(0, __VA_ARGS__) +#define npyv_set_f32(...) npyv_setf_f32(0, __VA_ARGS__) +#define npyv_set_f64(...) npyv_setf_f64(0, __VA_ARGS__) + +// Per lane select +#define npyv_select_u8(MASK, A, B) vec_sel(B, A, MASK) +#define npyv_select_s8 npyv_select_u8 +#define npyv_select_u16 npyv_select_u8 +#define npyv_select_s16 npyv_select_u8 +#define npyv_select_u32 npyv_select_u8 +#define npyv_select_s32 npyv_select_u8 +#define npyv_select_u64 npyv_select_u8 +#define npyv_select_s64 npyv_select_u8 +#define npyv_select_f32 npyv_select_u8 +#define npyv_select_f64 npyv_select_u8 + +// Reinterpret +#define npyv_reinterpret_u8_u8(X) X +#define npyv_reinterpret_u8_s8(X) ((npyv_u8)X) +#define npyv_reinterpret_u8_u16 npyv_reinterpret_u8_s8 +#define npyv_reinterpret_u8_s16 npyv_reinterpret_u8_s8 +#define npyv_reinterpret_u8_u32 npyv_reinterpret_u8_s8 +#define npyv_reinterpret_u8_s32 npyv_reinterpret_u8_s8 +#define npyv_reinterpret_u8_u64 npyv_reinterpret_u8_s8 +#define npyv_reinterpret_u8_s64 npyv_reinterpret_u8_s8 +#define npyv_reinterpret_u8_f32 npyv_reinterpret_u8_s8 +#define npyv_reinterpret_u8_f64 npyv_reinterpret_u8_s8 + +#define npyv_reinterpret_s8_s8(X) X +#define npyv_reinterpret_s8_u8(X) ((npyv_s8)X) +#define npyv_reinterpret_s8_u16 npyv_reinterpret_s8_u8 +#define npyv_reinterpret_s8_s16 npyv_reinterpret_s8_u8 +#define npyv_reinterpret_s8_u32 npyv_reinterpret_s8_u8 +#define npyv_reinterpret_s8_s32 npyv_reinterpret_s8_u8 +#define npyv_reinterpret_s8_u64 npyv_reinterpret_s8_u8 +#define npyv_reinterpret_s8_s64 npyv_reinterpret_s8_u8 +#define npyv_reinterpret_s8_f32 npyv_reinterpret_s8_u8 +#define npyv_reinterpret_s8_f64 npyv_reinterpret_s8_u8 + +#define npyv_reinterpret_u16_u16(X) X +#define npyv_reinterpret_u16_u8(X) ((npyv_u16)X) +#define npyv_reinterpret_u16_s8 npyv_reinterpret_u16_u8 +#define npyv_reinterpret_u16_s16 npyv_reinterpret_u16_u8 +#define npyv_reinterpret_u16_u32 npyv_reinterpret_u16_u8 +#define npyv_reinterpret_u16_s32 npyv_reinterpret_u16_u8 +#define npyv_reinterpret_u16_u64 npyv_reinterpret_u16_u8 +#define npyv_reinterpret_u16_s64 npyv_reinterpret_u16_u8 +#define npyv_reinterpret_u16_f32 npyv_reinterpret_u16_u8 +#define npyv_reinterpret_u16_f64 npyv_reinterpret_u16_u8 + +#define npyv_reinterpret_s16_s16(X) X +#define npyv_reinterpret_s16_u8(X) ((npyv_s16)X) +#define npyv_reinterpret_s16_s8 npyv_reinterpret_s16_u8 +#define npyv_reinterpret_s16_u16 npyv_reinterpret_s16_u8 +#define npyv_reinterpret_s16_u32 npyv_reinterpret_s16_u8 +#define npyv_reinterpret_s16_s32 npyv_reinterpret_s16_u8 +#define npyv_reinterpret_s16_u64 npyv_reinterpret_s16_u8 +#define npyv_reinterpret_s16_s64 npyv_reinterpret_s16_u8 +#define npyv_reinterpret_s16_f32 npyv_reinterpret_s16_u8 +#define npyv_reinterpret_s16_f64 npyv_reinterpret_s16_u8 + +#define npyv_reinterpret_u32_u32(X) X +#define npyv_reinterpret_u32_u8(X) ((npyv_u32)X) +#define npyv_reinterpret_u32_s8 npyv_reinterpret_u32_u8 +#define npyv_reinterpret_u32_u16 npyv_reinterpret_u32_u8 +#define npyv_reinterpret_u32_s16 npyv_reinterpret_u32_u8 +#define npyv_reinterpret_u32_s32 npyv_reinterpret_u32_u8 +#define npyv_reinterpret_u32_u64 npyv_reinterpret_u32_u8 +#define npyv_reinterpret_u32_s64 npyv_reinterpret_u32_u8 +#define npyv_reinterpret_u32_f32 npyv_reinterpret_u32_u8 +#define npyv_reinterpret_u32_f64 npyv_reinterpret_u32_u8 + +#define npyv_reinterpret_s32_s32(X) X +#define npyv_reinterpret_s32_u8(X) ((npyv_s32)X) +#define npyv_reinterpret_s32_s8 npyv_reinterpret_s32_u8 +#define npyv_reinterpret_s32_u16 npyv_reinterpret_s32_u8 +#define npyv_reinterpret_s32_s16 npyv_reinterpret_s32_u8 +#define npyv_reinterpret_s32_u32 npyv_reinterpret_s32_u8 +#define npyv_reinterpret_s32_u64 npyv_reinterpret_s32_u8 +#define npyv_reinterpret_s32_s64 npyv_reinterpret_s32_u8 +#define npyv_reinterpret_s32_f32 npyv_reinterpret_s32_u8 +#define npyv_reinterpret_s32_f64 npyv_reinterpret_s32_u8 + +#define npyv_reinterpret_u64_u64(X) X +#define npyv_reinterpret_u64_u8(X) ((npyv_u64)X) +#define npyv_reinterpret_u64_s8 npyv_reinterpret_u64_u8 +#define npyv_reinterpret_u64_u16 npyv_reinterpret_u64_u8 +#define npyv_reinterpret_u64_s16 npyv_reinterpret_u64_u8 +#define npyv_reinterpret_u64_u32 npyv_reinterpret_u64_u8 +#define npyv_reinterpret_u64_s32 npyv_reinterpret_u64_u8 +#define npyv_reinterpret_u64_s64 npyv_reinterpret_u64_u8 +#define npyv_reinterpret_u64_f32 npyv_reinterpret_u64_u8 +#define npyv_reinterpret_u64_f64 npyv_reinterpret_u64_u8 + +#define npyv_reinterpret_s64_s64(X) X +#define npyv_reinterpret_s64_u8(X) ((npyv_s64)X) +#define npyv_reinterpret_s64_s8 npyv_reinterpret_s64_u8 +#define npyv_reinterpret_s64_u16 npyv_reinterpret_s64_u8 +#define npyv_reinterpret_s64_s16 npyv_reinterpret_s64_u8 +#define npyv_reinterpret_s64_u32 npyv_reinterpret_s64_u8 +#define npyv_reinterpret_s64_s32 npyv_reinterpret_s64_u8 +#define npyv_reinterpret_s64_u64 npyv_reinterpret_s64_u8 +#define npyv_reinterpret_s64_f32 npyv_reinterpret_s64_u8 +#define npyv_reinterpret_s64_f64 npyv_reinterpret_s64_u8 + +#define npyv_reinterpret_f32_f32(X) X +#define npyv_reinterpret_f32_u8(X) ((npyv_f32)X) +#define npyv_reinterpret_f32_s8 npyv_reinterpret_f32_u8 +#define npyv_reinterpret_f32_u16 npyv_reinterpret_f32_u8 +#define npyv_reinterpret_f32_s16 npyv_reinterpret_f32_u8 +#define npyv_reinterpret_f32_u32 npyv_reinterpret_f32_u8 +#define npyv_reinterpret_f32_s32 npyv_reinterpret_f32_u8 +#define npyv_reinterpret_f32_u64 npyv_reinterpret_f32_u8 +#define npyv_reinterpret_f32_s64 npyv_reinterpret_f32_u8 +#define npyv_reinterpret_f32_f64 npyv_reinterpret_f32_u8 + +#define npyv_reinterpret_f64_f64(X) X +#define npyv_reinterpret_f64_u8(X) ((npyv_f64)X) +#define npyv_reinterpret_f64_s8 npyv_reinterpret_f64_u8 +#define npyv_reinterpret_f64_u16 npyv_reinterpret_f64_u8 +#define npyv_reinterpret_f64_s16 npyv_reinterpret_f64_u8 +#define npyv_reinterpret_f64_u32 npyv_reinterpret_f64_u8 +#define npyv_reinterpret_f64_s32 npyv_reinterpret_f64_u8 +#define npyv_reinterpret_f64_u64 npyv_reinterpret_f64_u8 +#define npyv_reinterpret_f64_s64 npyv_reinterpret_f64_u8 +#define npyv_reinterpret_f64_f32 npyv_reinterpret_f64_u8 + +// Only required by AVX2/AVX512 +#define npyv_cleanup() ((void)0) + +#endif // _NPY_SIMD_VSX_MISC_H diff --git a/numpy/core/src/common/simd/vsx/operators.h b/numpy/core/src/common/simd/vsx/operators.h new file mode 100644 index 000000000..ca020d9e0 --- /dev/null +++ b/numpy/core/src/common/simd/vsx/operators.h @@ -0,0 +1,216 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_VSX_OPERATORS_H +#define _NPY_SIMD_VSX_OPERATORS_H + +/*************************** + * Shifting + ***************************/ + +// Left +#define npyv_shl_u16(A, C) vec_sl(A, npyv_setall_u16(C)) +#define npyv_shl_s16(A, C) vec_sl(A, npyv_setall_u16(C)) +#define npyv_shl_u32(A, C) vec_sl(A, npyv_setall_u32(C)) +#define npyv_shl_s32(A, C) vec_sl(A, npyv_setall_u32(C)) +#define npyv_shl_u64(A, C) vec_sl(A, npyv_setall_u64(C)) +#define npyv_shl_s64(A, C) vec_sl(A, npyv_setall_u64(C)) + +// Left by an immediate constant +#define npyv_shli_u16 npyv_shl_u16 +#define npyv_shli_s16 npyv_shl_s16 +#define npyv_shli_u32 npyv_shl_u32 +#define npyv_shli_s32 npyv_shl_s32 +#define npyv_shli_u64 npyv_shl_u64 +#define npyv_shli_s64 npyv_shl_s64 + +// Right +#define npyv_shr_u16(A, C) vec_sr(A, npyv_setall_u16(C)) +#define npyv_shr_s16(A, C) vec_sra(A, npyv_setall_u16(C)) +#define npyv_shr_u32(A, C) vec_sr(A, npyv_setall_u32(C)) +#define npyv_shr_s32(A, C) vec_sra(A, npyv_setall_u32(C)) +#define npyv_shr_u64(A, C) vec_sr(A, npyv_setall_u64(C)) +#define npyv_shr_s64(A, C) vec_sra(A, npyv_setall_u64(C)) + +// Right by an immediate constant +#define npyv_shri_u16 npyv_shr_u16 +#define npyv_shri_s16 npyv_shr_s16 +#define npyv_shri_u32 npyv_shr_u32 +#define npyv_shri_s32 npyv_shr_s32 +#define npyv_shri_u64 npyv_shr_u64 +#define npyv_shri_s64 npyv_shr_s64 + +/*************************** + * Logical + ***************************/ + +// AND +#define npyv_and_u8 vec_and +#define npyv_and_s8 vec_and +#define npyv_and_u16 vec_and +#define npyv_and_s16 vec_and +#define npyv_and_u32 vec_and +#define npyv_and_s32 vec_and +#define npyv_and_u64 vec_and +#define npyv_and_s64 vec_and +#define npyv_and_f32 vec_and +#define npyv_and_f64 vec_and + +// OR +#define npyv_or_u8 vec_or +#define npyv_or_s8 vec_or +#define npyv_or_u16 vec_or +#define npyv_or_s16 vec_or +#define npyv_or_u32 vec_or +#define npyv_or_s32 vec_or +#define npyv_or_u64 vec_or +#define npyv_or_s64 vec_or +#define npyv_or_f32 vec_or +#define npyv_or_f64 vec_or + +// XOR +#define npyv_xor_u8 vec_xor +#define npyv_xor_s8 vec_xor +#define npyv_xor_u16 vec_xor +#define npyv_xor_s16 vec_xor +#define npyv_xor_u32 vec_xor +#define npyv_xor_s32 vec_xor +#define npyv_xor_u64 vec_xor +#define npyv_xor_s64 vec_xor +#define npyv_xor_f32 vec_xor +#define npyv_xor_f64 vec_xor + +// NOT +// note: we implement npyv_not_b*(boolen types) for internal use*/ +#define NPYV_IMPL_VSX_NOT_INT(VEC_LEN) \ + NPY_FINLINE npyv_u##VEC_LEN npyv_not_u##VEC_LEN(npyv_u##VEC_LEN a) \ + { return vec_nor(a, a); } \ + NPY_FINLINE npyv_s##VEC_LEN npyv_not_s##VEC_LEN(npyv_s##VEC_LEN a) \ + { return vec_nor(a, a); } \ + NPY_FINLINE npyv_b##VEC_LEN npyv_not_b##VEC_LEN(npyv_b##VEC_LEN a) \ + { return vec_nor(a, a); } + +NPYV_IMPL_VSX_NOT_INT(8) +NPYV_IMPL_VSX_NOT_INT(16) +NPYV_IMPL_VSX_NOT_INT(32) + +// up to gcc5 vec_nor doesn't support bool long long +#if defined(__GNUC__) && __GNUC__ > 5 + NPYV_IMPL_VSX_NOT_INT(64) +#else + NPY_FINLINE npyv_u64 npyv_not_u64(npyv_u64 a) + { return vec_nor(a, a); } + NPY_FINLINE npyv_s64 npyv_not_s64(npyv_s64 a) + { return vec_nor(a, a); } + NPY_FINLINE npyv_b64 npyv_not_b64(npyv_b64 a) + { return (npyv_b64)vec_nor((npyv_u64)a, (npyv_u64)a); } +#endif + +NPY_FINLINE npyv_f32 npyv_not_f32(npyv_f32 a) +{ return vec_nor(a, a); } +NPY_FINLINE npyv_f64 npyv_not_f64(npyv_f64 a) +{ return vec_nor(a, a); } + +/*************************** + * Comparison + ***************************/ + +// Int Equal +#define npyv_cmpeq_u8 vec_cmpeq +#define npyv_cmpeq_s8 vec_cmpeq +#define npyv_cmpeq_u16 vec_cmpeq +#define npyv_cmpeq_s16 vec_cmpeq +#define npyv_cmpeq_u32 vec_cmpeq +#define npyv_cmpeq_s32 vec_cmpeq +#define npyv_cmpeq_u64 vec_cmpeq +#define npyv_cmpeq_s64 vec_cmpeq +#define npyv_cmpeq_f32 vec_cmpeq +#define npyv_cmpeq_f64 vec_cmpeq + +// Int Not Equal +#ifdef NPY_HAVE_VSX3 + #define npyv_cmpneq_u8 vec_cmpne + #define npyv_cmpneq_s8 vec_cmpne + #define npyv_cmpneq_u16 vec_cmpne + #define npyv_cmpneq_s16 vec_cmpne + #define npyv_cmpneq_u32 vec_cmpne + #define npyv_cmpneq_s32 vec_cmpne + #define npyv_cmpneq_u64 vec_cmpne + #define npyv_cmpneq_s64 vec_cmpne + #define npyv_cmpneq_f32 vec_cmpne + #define npyv_cmpneq_f64 vec_cmpne +#else + #define npyv_cmpneq_u8(A, B) npyv_not_b8(vec_cmpeq(A, B)) + #define npyv_cmpneq_s8(A, B) npyv_not_b8(vec_cmpeq(A, B)) + #define npyv_cmpneq_u16(A, B) npyv_not_b16(vec_cmpeq(A, B)) + #define npyv_cmpneq_s16(A, B) npyv_not_b16(vec_cmpeq(A, B)) + #define npyv_cmpneq_u32(A, B) npyv_not_b32(vec_cmpeq(A, B)) + #define npyv_cmpneq_s32(A, B) npyv_not_b32(vec_cmpeq(A, B)) + #define npyv_cmpneq_u64(A, B) npyv_not_b64(vec_cmpeq(A, B)) + #define npyv_cmpneq_s64(A, B) npyv_not_b64(vec_cmpeq(A, B)) + #define npyv_cmpneq_f32(A, B) npyv_not_b32(vec_cmpeq(A, B)) + #define npyv_cmpneq_f64(A, B) npyv_not_b64(vec_cmpeq(A, B)) +#endif + +// Greater than +#define npyv_cmpgt_u8 vec_cmpgt +#define npyv_cmpgt_s8 vec_cmpgt +#define npyv_cmpgt_u16 vec_cmpgt +#define npyv_cmpgt_s16 vec_cmpgt +#define npyv_cmpgt_u32 vec_cmpgt +#define npyv_cmpgt_s32 vec_cmpgt +#define npyv_cmpgt_u64 vec_cmpgt +#define npyv_cmpgt_s64 vec_cmpgt +#define npyv_cmpgt_f32 vec_cmpgt +#define npyv_cmpgt_f64 vec_cmpgt + +// Greater than or equal +// up to gcc5 vec_cmpge only supports single and double precision +#if defined(__GNUC__) && __GNUC__ > 5 + #define npyv_cmpge_u8 vec_cmpge + #define npyv_cmpge_s8 vec_cmpge + #define npyv_cmpge_u16 vec_cmpge + #define npyv_cmpge_s16 vec_cmpge + #define npyv_cmpge_u32 vec_cmpge + #define npyv_cmpge_s32 vec_cmpge + #define npyv_cmpge_u64 vec_cmpge + #define npyv_cmpge_s64 vec_cmpge +#else + #define npyv_cmpge_u8(A, B) npyv_not_b8(vec_cmpgt(B, A)) + #define npyv_cmpge_s8(A, B) npyv_not_b8(vec_cmpgt(B, A)) + #define npyv_cmpge_u16(A, B) npyv_not_b16(vec_cmpgt(B, A)) + #define npyv_cmpge_s16(A, B) npyv_not_b16(vec_cmpgt(B, A)) + #define npyv_cmpge_u32(A, B) npyv_not_b32(vec_cmpgt(B, A)) + #define npyv_cmpge_s32(A, B) npyv_not_b32(vec_cmpgt(B, A)) + #define npyv_cmpge_u64(A, B) npyv_not_b64(vec_cmpgt(B, A)) + #define npyv_cmpge_s64(A, B) npyv_not_b64(vec_cmpgt(B, A)) +#endif +#define npyv_cmpge_f32 vec_cmpge +#define npyv_cmpge_f64 vec_cmpge + +// Less than +#define npyv_cmplt_u8(A, B) npyv_cmpgt_u8(B, A) +#define npyv_cmplt_s8(A, B) npyv_cmpgt_s8(B, A) +#define npyv_cmplt_u16(A, B) npyv_cmpgt_u16(B, A) +#define npyv_cmplt_s16(A, B) npyv_cmpgt_s16(B, A) +#define npyv_cmplt_u32(A, B) npyv_cmpgt_u32(B, A) +#define npyv_cmplt_s32(A, B) npyv_cmpgt_s32(B, A) +#define npyv_cmplt_u64(A, B) npyv_cmpgt_u64(B, A) +#define npyv_cmplt_s64(A, B) npyv_cmpgt_s64(B, A) +#define npyv_cmplt_f32(A, B) npyv_cmpgt_f32(B, A) +#define npyv_cmplt_f64(A, B) npyv_cmpgt_f64(B, A) + +// Less than or equal +#define npyv_cmple_u8(A, B) npyv_cmpge_u8(B, A) +#define npyv_cmple_s8(A, B) npyv_cmpge_s8(B, A) +#define npyv_cmple_u16(A, B) npyv_cmpge_u16(B, A) +#define npyv_cmple_s16(A, B) npyv_cmpge_s16(B, A) +#define npyv_cmple_u32(A, B) npyv_cmpge_u32(B, A) +#define npyv_cmple_s32(A, B) npyv_cmpge_s32(B, A) +#define npyv_cmple_u64(A, B) npyv_cmpge_u64(B, A) +#define npyv_cmple_s64(A, B) npyv_cmpge_s64(B, A) +#define npyv_cmple_f32(A, B) npyv_cmpge_f32(B, A) +#define npyv_cmple_f64(A, B) npyv_cmpge_f64(B, A) + +#endif // _NPY_SIMD_VSX_OPERATORS_H diff --git a/numpy/core/src/common/simd/vsx/reorder.h b/numpy/core/src/common/simd/vsx/reorder.h new file mode 100644 index 000000000..bfb9115fa --- /dev/null +++ b/numpy/core/src/common/simd/vsx/reorder.h @@ -0,0 +1,65 @@ +#ifndef NPY_SIMD + #error "Not a standalone header" +#endif + +#ifndef _NPY_SIMD_VSX_REORDER_H +#define _NPY_SIMD_VSX_REORDER_H + +// combine lower part of two vectors +#define npyv__combinel(A, B) vec_mergeh((npyv_u64)(A), (npyv_u64)(B)) +#define npyv_combinel_u8(A, B) ((npyv_u8) npyv__combinel(A, B)) +#define npyv_combinel_s8(A, B) ((npyv_s8) npyv__combinel(A, B)) +#define npyv_combinel_u16(A, B) ((npyv_u16)npyv__combinel(A, B)) +#define npyv_combinel_s16(A, B) ((npyv_s16)npyv__combinel(A, B)) +#define npyv_combinel_u32(A, B) ((npyv_u32)npyv__combinel(A, B)) +#define npyv_combinel_s32(A, B) ((npyv_s32)npyv__combinel(A, B)) +#define npyv_combinel_u64 vec_mergeh +#define npyv_combinel_s64 vec_mergeh +#define npyv_combinel_f32(A, B) ((npyv_f32)npyv__combinel(A, B)) +#define npyv_combinel_f64 vec_mergeh + +// combine higher part of two vectors +#define npyv__combineh(A, B) vec_mergel((npyv_u64)(A), (npyv_u64)(B)) +#define npyv_combineh_u8(A, B) ((npyv_u8) npyv__combineh(A, B)) +#define npyv_combineh_s8(A, B) ((npyv_s8) npyv__combineh(A, B)) +#define npyv_combineh_u16(A, B) ((npyv_u16)npyv__combineh(A, B)) +#define npyv_combineh_s16(A, B) ((npyv_s16)npyv__combineh(A, B)) +#define npyv_combineh_u32(A, B) ((npyv_u32)npyv__combineh(A, B)) +#define npyv_combineh_s32(A, B) ((npyv_s32)npyv__combineh(A, B)) +#define npyv_combineh_u64 vec_mergel +#define npyv_combineh_s64 vec_mergel +#define npyv_combineh_f32(A, B) ((npyv_f32)npyv__combineh(A, B)) +#define npyv_combineh_f64 vec_mergel + +/* + * combine: combine two vectors from lower and higher parts of two other vectors + * zip: interleave two vectors +*/ +#define NPYV_IMPL_VSX_COMBINE_ZIP(T_VEC, SFX) \ + NPY_FINLINE T_VEC##x2 npyv_combine_##SFX(T_VEC a, T_VEC b) \ + { \ + T_VEC##x2 r; \ + r.val[0] = NPY_CAT(npyv_combinel_, SFX)(a, b); \ + r.val[1] = NPY_CAT(npyv_combineh_, SFX)(a, b); \ + return r; \ + } \ + NPY_FINLINE T_VEC##x2 npyv_zip_##SFX(T_VEC a, T_VEC b) \ + { \ + T_VEC##x2 r; \ + r.val[0] = vec_mergeh(a, b); \ + r.val[1] = vec_mergel(a, b); \ + return r; \ + } + +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_u8, u8) +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_s8, s8) +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_u16, u16) +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_s16, s16) +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_u32, u32) +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_s32, s32) +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_u64, u64) +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_s64, s64) +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_f32, f32) +NPYV_IMPL_VSX_COMBINE_ZIP(npyv_f64, f64) + +#endif // _NPY_SIMD_VSX_REORDER_H diff --git a/numpy/core/src/common/simd/vsx/vsx.h b/numpy/core/src/common/simd/vsx/vsx.h new file mode 100644 index 000000000..5525dc1e6 --- /dev/null +++ b/numpy/core/src/common/simd/vsx/vsx.h @@ -0,0 +1,64 @@ +#ifndef _NPY_SIMD_H_ + #error "Not a standalone header" +#endif + +#define NPY_SIMD 128 +#define NPY_SIMD_WIDTH 16 +#define NPY_SIMD_F64 1 + +typedef __vector unsigned char npyv_u8; +typedef __vector signed char npyv_s8; +typedef __vector unsigned short npyv_u16; +typedef __vector signed short npyv_s16; +typedef __vector unsigned int npyv_u32; +typedef __vector signed int npyv_s32; +typedef __vector unsigned long long npyv_u64; +typedef __vector signed long long npyv_s64; +typedef __vector float npyv_f32; +typedef __vector double npyv_f64; + +typedef struct { npyv_u8 val[2]; } npyv_u8x2; +typedef struct { npyv_s8 val[2]; } npyv_s8x2; +typedef struct { npyv_u16 val[2]; } npyv_u16x2; +typedef struct { npyv_s16 val[2]; } npyv_s16x2; +typedef struct { npyv_u32 val[2]; } npyv_u32x2; +typedef struct { npyv_s32 val[2]; } npyv_s32x2; +typedef struct { npyv_u64 val[2]; } npyv_u64x2; +typedef struct { npyv_s64 val[2]; } npyv_s64x2; +typedef struct { npyv_f32 val[2]; } npyv_f32x2; +typedef struct { npyv_f64 val[2]; } npyv_f64x2; + +typedef struct { npyv_u8 val[3]; } npyv_u8x3; +typedef struct { npyv_s8 val[3]; } npyv_s8x3; +typedef struct { npyv_u16 val[3]; } npyv_u16x3; +typedef struct { npyv_s16 val[3]; } npyv_s16x3; +typedef struct { npyv_u32 val[3]; } npyv_u32x3; +typedef struct { npyv_s32 val[3]; } npyv_s32x3; +typedef struct { npyv_u64 val[3]; } npyv_u64x3; +typedef struct { npyv_s64 val[3]; } npyv_s64x3; +typedef struct { npyv_f32 val[3]; } npyv_f32x3; +typedef struct { npyv_f64 val[3]; } npyv_f64x3; + +#define npyv_nlanes_u8 16 +#define npyv_nlanes_s8 16 +#define npyv_nlanes_u16 8 +#define npyv_nlanes_s16 8 +#define npyv_nlanes_u32 4 +#define npyv_nlanes_s32 4 +#define npyv_nlanes_u64 2 +#define npyv_nlanes_s64 2 +#define npyv_nlanes_f32 4 +#define npyv_nlanes_f64 2 + +// using __bool with typdef cause ambiguous errors +#define npyv_b8 __vector __bool char +#define npyv_b16 __vector __bool short +#define npyv_b32 __vector __bool int +#define npyv_b64 __vector __bool long long + +#include "memory.h" +#include "misc.h" +#include "reorder.h" +#include "operators.h" +#include "conversion.h" +#include "arithmetic.h" diff --git a/numpy/core/src/multiarray/_datetime.h b/numpy/core/src/multiarray/_datetime.h index 20f7a132c..4e7ade5ed 100644 --- a/numpy/core/src/multiarray/_datetime.h +++ b/numpy/core/src/multiarray/_datetime.h @@ -38,6 +38,10 @@ create_datetime_dtype_with_unit(int type_num, NPY_DATETIMEUNIT unit); NPY_NO_EXPORT PyArray_DatetimeMetaData * get_datetime_metadata_from_dtype(PyArray_Descr *dtype); +NPY_NO_EXPORT int +find_string_array_datetime64_type(PyArrayObject *arr, + PyArray_DatetimeMetaData *meta); + /* * Both type1 and type2 must be either NPY_DATETIME or NPY_TIMEDELTA. * Applies the type promotion rules between the two types, returning diff --git a/numpy/core/src/multiarray/abstractdtypes.c b/numpy/core/src/multiarray/abstractdtypes.c new file mode 100644 index 000000000..02c0eac53 --- /dev/null +++ b/numpy/core/src/multiarray/abstractdtypes.c @@ -0,0 +1,168 @@ +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#include "structmember.h" + + +#define NPY_NO_DEPRECATED_API NPY_API_VERSION +#define _MULTIARRAYMODULE +#include "numpy/ndarraytypes.h" +#include "numpy/arrayobject.h" + +#include "abstractdtypes.h" +#include "array_coercion.h" +#include "common.h" + + +static PyArray_Descr * +discover_descriptor_from_pyint( + PyArray_DTypeMeta *NPY_UNUSED(cls), PyObject *obj) +{ + assert(PyLong_Check(obj)); + /* + * We check whether long is good enough. If not, check longlong and + * unsigned long before falling back to `object`. + */ + long long value = PyLong_AsLongLong(obj); + if (error_converting(value)) { + PyErr_Clear(); + } + else { + if (NPY_MIN_LONG <= value && value <= NPY_MAX_LONG) { + return PyArray_DescrFromType(NPY_LONG); + } + return PyArray_DescrFromType(NPY_LONGLONG); + } + + unsigned long long uvalue = PyLong_AsUnsignedLongLong(obj); + if (uvalue == (unsigned long long)-1 && PyErr_Occurred()){ + PyErr_Clear(); + } + else { + return PyArray_DescrFromType(NPY_ULONGLONG); + } + + return PyArray_DescrFromType(NPY_OBJECT); +} + + +static PyArray_Descr* +discover_descriptor_from_pyfloat( + PyArray_DTypeMeta* NPY_UNUSED(cls), PyObject *obj) +{ + assert(PyFloat_CheckExact(obj)); + return PyArray_DescrFromType(NPY_DOUBLE); +} + + +static PyArray_Descr* +discover_descriptor_from_pycomplex( + PyArray_DTypeMeta* NPY_UNUSED(cls), PyObject *obj) +{ + assert(PyComplex_CheckExact(obj)); + return PyArray_DescrFromType(NPY_COMPLEX128); +} + + +NPY_NO_EXPORT int +initialize_and_map_pytypes_to_dtypes() +{ + PyArrayAbstractObjDTypeMeta_Type.tp_base = &PyArrayDTypeMeta_Type; + if (PyType_Ready(&PyArrayAbstractObjDTypeMeta_Type) < 0) { + return -1; + } + ((PyTypeObject *)&PyArray_PyIntAbstractDType)->tp_base = &PyArrayDTypeMeta_Type; + PyArray_PyIntAbstractDType.scalar_type = &PyLong_Type; + if (PyType_Ready((PyTypeObject *)&PyArray_PyIntAbstractDType) < 0) { + return -1; + } + ((PyTypeObject *)&PyArray_PyFloatAbstractDType)->tp_base = &PyArrayDTypeMeta_Type; + PyArray_PyFloatAbstractDType.scalar_type = &PyFloat_Type; + if (PyType_Ready((PyTypeObject *)&PyArray_PyFloatAbstractDType) < 0) { + return -1; + } + ((PyTypeObject *)&PyArray_PyComplexAbstractDType)->tp_base = &PyArrayDTypeMeta_Type; + PyArray_PyComplexAbstractDType.scalar_type = &PyComplex_Type; + if (PyType_Ready((PyTypeObject *)&PyArray_PyComplexAbstractDType) < 0) { + return -1; + } + + /* Register the new DTypes for discovery */ + if (_PyArray_MapPyTypeToDType( + &PyArray_PyIntAbstractDType, &PyLong_Type, NPY_FALSE) < 0) { + return -1; + } + if (_PyArray_MapPyTypeToDType( + &PyArray_PyFloatAbstractDType, &PyFloat_Type, NPY_FALSE) < 0) { + return -1; + } + if (_PyArray_MapPyTypeToDType( + &PyArray_PyComplexAbstractDType, &PyComplex_Type, NPY_FALSE) < 0) { + return -1; + } + + /* + * Map str, bytes, and bool, for which we do not need abstract versions + * to the NumPy DTypes. This is done here using the `is_known_scalar_type` + * function. + * TODO: The `is_known_scalar_type` function is considered preliminary, + * the same could be achieved e.g. with additional abstract DTypes. + */ + PyArray_DTypeMeta *dtype; + dtype = NPY_DTYPE(PyArray_DescrFromType(NPY_UNICODE)); + if (_PyArray_MapPyTypeToDType(dtype, &PyUnicode_Type, NPY_FALSE) < 0) { + return -1; + } + + dtype = NPY_DTYPE(PyArray_DescrFromType(NPY_STRING)); + if (_PyArray_MapPyTypeToDType(dtype, &PyBytes_Type, NPY_FALSE) < 0) { + return -1; + } + dtype = NPY_DTYPE(PyArray_DescrFromType(NPY_BOOL)); + if (_PyArray_MapPyTypeToDType(dtype, &PyBool_Type, NPY_FALSE) < 0) { + return -1; + } + + return 0; +} + + + +/* Note: This is currently largely not used, but will be required eventually. */ +NPY_NO_EXPORT PyTypeObject PyArrayAbstractObjDTypeMeta_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "numpy._AbstractObjDTypeMeta", + .tp_basicsize = sizeof(PyArray_DTypeMeta), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "Helper MetaClass for value based casting AbstractDTypes.", +}; + +NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyIntAbstractDType = {{{ + PyVarObject_HEAD_INIT(&PyArrayAbstractObjDTypeMeta_Type, 0) + .tp_basicsize = sizeof(PyArray_DTypeMeta), + .tp_name = "numpy._PyIntBaseAbstractDType", + },}, + .abstract = 1, + .discover_descr_from_pyobject = discover_descriptor_from_pyint, + .kind = 'i', +}; + +NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyFloatAbstractDType = {{{ + PyVarObject_HEAD_INIT(&PyArrayAbstractObjDTypeMeta_Type, 0) + .tp_basicsize = sizeof(PyArray_DTypeMeta), + .tp_name = "numpy._PyFloatBaseAbstractDType", + },}, + .abstract = 1, + .discover_descr_from_pyobject = discover_descriptor_from_pyfloat, + .kind = 'f', +}; + +NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyComplexAbstractDType = {{{ + PyVarObject_HEAD_INIT(&PyArrayAbstractObjDTypeMeta_Type, 0) + .tp_basicsize = sizeof(PyArray_DTypeMeta), + .tp_name = "numpy._PyComplexBaseAbstractDType", + },}, + .abstract = 1, + .discover_descr_from_pyobject = discover_descriptor_from_pycomplex, + .kind = 'c', +}; + diff --git a/numpy/core/src/multiarray/abstractdtypes.h b/numpy/core/src/multiarray/abstractdtypes.h new file mode 100644 index 000000000..3a982cd38 --- /dev/null +++ b/numpy/core/src/multiarray/abstractdtypes.h @@ -0,0 +1,19 @@ +#ifndef _NPY_ABSTRACTDTYPES_H +#define _NPY_ABSTRACTDTYPES_H + +#include "dtypemeta.h" + +/* + * These are mainly needed for value based promotion in ufuncs. It + * may be necessary to make them (partially) public, to allow user-defined + * dtypes to perform value based casting. + */ +NPY_NO_EXPORT extern PyTypeObject PyArrayAbstractObjDTypeMeta_Type; +NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyIntAbstractDType; +NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyFloatAbstractDType; +NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyComplexAbstractDType; + +NPY_NO_EXPORT int +initialize_and_map_pytypes_to_dtypes(void); + +#endif /*_NPY_ABSTRACTDTYPES_H */ diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c new file mode 100644 index 000000000..9d367da1f --- /dev/null +++ b/numpy/core/src/multiarray/array_coercion.c @@ -0,0 +1,1430 @@ +#define NPY_NO_DEPRECATED_API NPY_API_VERSION +#define _UMATHMODULE +#define _MULTIARRAYMODULE + +#include "Python.h" + +#include "numpy/npy_3kcompat.h" + +#include "lowlevel_strided_loops.h" +#include "numpy/arrayobject.h" + +#include "descriptor.h" +#include "convert_datatype.h" +#include "dtypemeta.h" + +#include "array_coercion.h" +#include "ctors.h" +#include "common.h" +#include "_datetime.h" +#include "npy_import.h" + + +/* + * This file defines helpers for some of the ctors.c functions which + * create an array from Python sequences and types. + * When creating an array with ``np.array(...)`` we have to do two main things: + * + * 1. Find the exact shape of the resulting array + * 2. Find the correct dtype of the resulting array. + * + * In most cases these two things are can be done in a single processing step. + * There are in principle three different calls that should be distinguished: + * + * 1. The user calls ``np.array(..., dtype=np.dtype("<f8"))`` + * 2. The user calls ``np.array(..., dtype="S")`` + * 3. The user calls ``np.array(...)`` + * + * In the first case, in principle only the shape needs to be found. In the + * second case, the DType class (e.g. string) is already known but the DType + * instance (e.g. length of the string) has to be found. + * In the last case the DType class needs to be found as well. Note that + * it is not necessary to find the DType class of the entire array, but + * the DType class needs to be found for each element before the actual + * dtype instance can be found. + * + * Further, there are a few other things to keep in mind when coercing arrays: + * + * * For UFunc promotion, Python scalars need to be handled specially to + * allow value based casting. This requires python complex/float to + * have their own DTypes. + * * It is necessary to decide whether or not a sequence is an element. + * For example tuples are considered elements for structured dtypes, but + * otherwise are considered sequences. + * This means that if a dtype is given (either as a class or instance), + * it can effect the dimension discovery part. + * For the "special" NumPy types structured void and "c" (single character) + * this is special cased. For future user-types, this is currently + * handled by providing calling an `is_known_scalar` method. This method + * currently ensures that Python numerical types are handled quickly. + * + * In the initial version of this implementation, it is assumed that dtype + * discovery can be implemented sufficiently fast. That is, it is not + * necessary to create fast paths that only find the correct shape e.g. when + * ``dtype=np.dtype("f8")`` is given. + * + * The code here avoid multiple conversion of array-like objects (including + * sequences). These objects are cached after conversion, which will require + * additional memory, but can drastically speed up coercion from from array + * like objects. + */ + + +/* + * For finding a DType quickly from a type, it is easiest to have a + * a mapping of pytype -> DType. + * TODO: This mapping means that it is currently impossible to delete a + * pair of pytype <-> DType. To resolve this, it is necessary to + * weakly reference the pytype. As long as the pytype is alive, we + * want to be able to use `np.array([pytype()])`. + * It should be possible to retrofit this without too much trouble + * (all type objects support weak references). + */ +PyObject *_global_pytype_to_type_dict = NULL; + + +/* Enum to track or signal some things during dtype and shape discovery */ +enum _dtype_discovery_flags { + FOUND_RAGGED_ARRAY = 1 << 0, + GAVE_SUBCLASS_WARNING = 1 << 1, + PROMOTION_FAILED = 1 << 2, + DISCOVER_STRINGS_AS_SEQUENCES = 1 << 3, + DISCOVER_TUPLES_AS_ELEMENTS = 1 << 4, + MAX_DIMS_WAS_REACHED = 1 << 5, + DESCRIPTOR_WAS_SET = 1 << 6, +}; + + +/** + * Adds known sequence types to the global type dictionary, note that when + * a DType is passed in, this lookup may be ignored. + * + * @return -1 on error 0 on success + */ +static int +_prime_global_pytype_to_type_dict(void) +{ + int res; + + /* Add the basic Python sequence types */ + res = PyDict_SetItem(_global_pytype_to_type_dict, + (PyObject *)&PyList_Type, Py_None); + if (res < 0) { + return -1; + } + res = PyDict_SetItem(_global_pytype_to_type_dict, + (PyObject *)&PyTuple_Type, Py_None); + if (res < 0) { + return -1; + } + /* NumPy Arrays are not handled as scalars */ + res = PyDict_SetItem(_global_pytype_to_type_dict, + (PyObject *)&PyArray_Type, Py_None); + if (res < 0) { + return -1; + } + return 0; +} + + +/** + * Add a new mapping from a python type to the DType class. + * + * This assumes that the DType class is guaranteed to hold on the + * python type (this assumption is guaranteed). + * This functionality supercedes ``_typenum_fromtypeobj``. + * + * @param DType DType to map the python type to + * @param pytype Python type to map from + * @param userdef Whether or not it is user defined. We ensure that user + * defined scalars subclass from our scalars (for now). + */ +NPY_NO_EXPORT int +_PyArray_MapPyTypeToDType( + PyArray_DTypeMeta *DType, PyTypeObject *pytype, npy_bool userdef) +{ + PyObject *Dtype_obj = (PyObject *)DType; + + if (userdef) { + /* + * It seems we did not strictly enforce this in the legacy dtype + * API, but assume that it is always true. Further, this could be + * relaxed in the future. In particular we should have a new + * superclass of ``np.generic`` in order to note enforce the array + * scalar behaviour. + */ + if (!PyObject_IsSubclass((PyObject *)pytype, (PyObject *)&PyGenericArrType_Type)) { + PyErr_Format(PyExc_RuntimeError, + "currently it is only possible to register a DType " + "for scalars deriving from `np.generic`, got '%S'.", + (PyObject *)pytype); + return -1; + } + } + + /* Create the global dictionary if it does not exist */ + if (NPY_UNLIKELY(_global_pytype_to_type_dict == NULL)) { + _global_pytype_to_type_dict = PyDict_New(); + if (_global_pytype_to_type_dict == NULL) { + return -1; + } + if (_prime_global_pytype_to_type_dict() < 0) { + return -1; + } + } + + int res = PyDict_Contains(_global_pytype_to_type_dict, (PyObject *)pytype); + if (res < 0) { + return -1; + } + else if (res) { + PyErr_SetString(PyExc_RuntimeError, + "Can only map one python type to DType."); + return -1; + } + + return PyDict_SetItem(_global_pytype_to_type_dict, + (PyObject *)pytype, Dtype_obj); +} + + +/** + * Lookup the DType for a registered known python scalar type. + * + * @param pytype Python Type to look up + * @return DType, None if it a known non-scalar, or NULL if an unknown object. + */ +static NPY_INLINE PyArray_DTypeMeta * +discover_dtype_from_pytype(PyTypeObject *pytype) +{ + PyObject *DType; + + if (pytype == &PyArray_Type) { + Py_INCREF(Py_None); + return (PyArray_DTypeMeta *)Py_None; + } + + DType = PyDict_GetItem(_global_pytype_to_type_dict, (PyObject *)pytype); + if (DType == NULL) { + /* the python type is not known */ + return NULL; + } + + Py_INCREF(DType); + if (DType == Py_None) { + return (PyArray_DTypeMeta *)Py_None; + } + assert(PyObject_TypeCheck(DType, (PyTypeObject *)&PyArrayDTypeMeta_Type)); + return (PyArray_DTypeMeta *)DType; +} + + +/** + * Find the correct DType class for the given python type. If flags is NULL + * this is not used to discover a dtype, but only for conversion to an + * existing dtype. In that case the Python (not NumPy) scalar subclass + * checks are skipped. + * + * @param obj The python object, mainly type(pyobj) is used, the object + * is passed to reuse existing code at this time only. + * @param flags Flags used to know if warnings were already given. If + * flags is NULL, this is not + * @param fixed_DType if not NULL, will be checked first for whether or not + * it can/wants to handle the (possible) scalar value. + * @return New reference to either a DType class, Py_None, or NULL on error. + */ +static NPY_INLINE PyArray_DTypeMeta * +discover_dtype_from_pyobject( + PyObject *obj, enum _dtype_discovery_flags *flags, + PyArray_DTypeMeta *fixed_DType) +{ + if (fixed_DType != NULL) { + /* + * Let the given DType handle the discovery. This is when the + * scalar-type matches exactly, or the DType signals that it can + * handle the scalar-type. (Even if it cannot handle here it may be + * asked to attempt to do so later, if no other matching DType exists.) + */ + if ((Py_TYPE(obj) == fixed_DType->scalar_type) || + (fixed_DType->is_known_scalar_type != NULL && + fixed_DType->is_known_scalar_type(fixed_DType, Py_TYPE(obj)))) { + Py_INCREF(fixed_DType); + return fixed_DType; + } + } + + PyArray_DTypeMeta *DType = discover_dtype_from_pytype(Py_TYPE(obj)); + if (DType != NULL) { + return DType; + } + /* + * At this point we have not found a clear mapping, but mainly for + * backward compatibility we have to make some further attempts at + * interpreting the input as a known scalar type. + */ + PyArray_Descr *legacy_descr; + if (PyArray_IsScalar(obj, Generic)) { + legacy_descr = PyArray_DescrFromScalar(obj); + if (legacy_descr == NULL) { + return NULL; + } + } + else if (flags == NULL) { + Py_INCREF(Py_None); + return (PyArray_DTypeMeta *)Py_None; + } + else if (PyBytes_Check(obj)) { + legacy_descr = PyArray_DescrFromType(NPY_BYTE); + } + else if (PyUnicode_Check(obj)) { + legacy_descr = PyArray_DescrFromType(NPY_UNICODE); + } + else { + legacy_descr = _array_find_python_scalar_type(obj); + } + + if (legacy_descr != NULL) { + DType = NPY_DTYPE(legacy_descr); + Py_INCREF(DType); + Py_DECREF(legacy_descr); + /* TODO: Enable warning about subclass handling */ + if (0 && !((*flags) & GAVE_SUBCLASS_WARNING)) { + if (DEPRECATE_FUTUREWARNING( + "in the future NumPy will not automatically find the " + "dtype for subclasses of scalars known to NumPy (i.e. " + "python types). Use the appropriate `dtype=...` to create " + "this array. This will use the `object` dtype or raise " + "an error in the future.") < 0) { + return NULL; + } + *flags |= GAVE_SUBCLASS_WARNING; + } + return DType; + } + Py_INCREF(Py_None); + return (PyArray_DTypeMeta *)Py_None; +} + + +/* + * This function should probably become public API eventually. At this + * time it is implemented by falling back to `PyArray_AdaptFlexibleDType`. + * We will use `CastingImpl[from, to].adjust_descriptors(...)` to implement + * this logic. + */ +static NPY_INLINE PyArray_Descr * +cast_descriptor_to_fixed_dtype( + PyArray_Descr *descr, PyArray_DTypeMeta *fixed_DType) +{ + if (fixed_DType == NULL) { + /* Nothing to do, we only need to promote the new dtype */ + Py_INCREF(descr); + return descr; + } + + if (!fixed_DType->parametric) { + /* + * Don't actually do anything, the default is always the result + * of any cast. + */ + return fixed_DType->default_descr(fixed_DType); + } + if (PyObject_TypeCheck((PyObject *)descr, (PyTypeObject *)fixed_DType)) { + Py_INCREF(descr); + return descr; + } + /* + * TODO: When this is implemented for all dtypes, the special cases + * can be removed... + */ + if (fixed_DType->legacy && fixed_DType->parametric && + NPY_DTYPE(descr)->legacy) { + PyArray_Descr *flex_dtype = PyArray_DescrFromType(fixed_DType->type_num); + return PyArray_AdaptFlexibleDType(descr, flex_dtype); + } + + PyErr_SetString(PyExc_NotImplementedError, + "Must use casting to find the correct dtype, this is " + "not yet implemented! " + "(It should not be possible to hit this code currently!)"); + return NULL; +} + + +/** + * Discover the correct descriptor from a known DType class and scalar. + * If the fixed DType can discover a dtype instance/descr all is fine, + * if it cannot and DType is used instead, a cast will have to be tried. + * + * @param fixed_DType A user provided fixed DType, can be NULL + * @param DType A discovered DType (by discover_dtype_from_pyobject); + * this can be identical to `fixed_DType`, if it obj is a + * known scalar. Can be `NULL` indicating no known type. + * @param obj The Python scalar object. At the time of calling this function + * it must be known that `obj` should represent a scalar. + */ +static NPY_INLINE PyArray_Descr * +find_scalar_descriptor( + PyArray_DTypeMeta *fixed_DType, PyArray_DTypeMeta *DType, + PyObject *obj) +{ + PyArray_Descr *descr; + + if (DType == NULL && fixed_DType == NULL) { + /* No known DType and no fixed one means we go to object. */ + return PyArray_DescrFromType(NPY_OBJECT); + } + else if (DType == NULL) { + /* + * If no DType is known/found, give the fixed give one a second + * chance. This allows for example string, to call `str(obj)` to + * figure out the length for arbitrary objects. + */ + descr = fixed_DType->discover_descr_from_pyobject(fixed_DType, obj); + } + else { + descr = DType->discover_descr_from_pyobject(DType, obj); + } + if (descr == NULL) { + return NULL; + } + if (fixed_DType == NULL) { + return descr; + } + + Py_SETREF(descr, cast_descriptor_to_fixed_dtype(descr, fixed_DType)); + return descr; +} + + +/** + * Assign a single element in an array from a python value. + * + * The dtypes SETITEM should only be trusted to generally do the right + * thing if something is known to be a scalar *and* is of a python type known + * to the DType (which should include all basic Python math types), but in + * general a cast may be necessary. + * This function handles the cast, which is for example hit when assigning + * a float128 to complex128. + * + * At this time, this function does not support arrays (historically we + * mainly supported arrays through `__float__()`, etc.). Such support should + * possibly be added (although when called from `PyArray_AssignFromCache` + * the input cannot be an array). + * Note that this is also problematic for some array-likes, such as + * `astropy.units.Quantity` and `np.ma.masked`. These are used to us calling + * `__float__`/`__int__` for 0-D instances in many cases. + * Eventually, we may want to define this as wrong: They must use DTypes + * instead of (only) subclasses. Until then, here as well as in + * `PyArray_AssignFromCache` (which already does this), we need to special + * case 0-D array-likes to behave like arbitrary (unknown!) Python objects. + * + * @param descr + * @param item + * @param value + * @return 0 on success -1 on failure. + */ +/* + * TODO: This function should possibly be public API. + */ +NPY_NO_EXPORT int +PyArray_Pack(PyArray_Descr *descr, char *item, PyObject *value) +{ + PyArrayObject_fields arr_fields = { + .flags = NPY_ARRAY_WRITEABLE, /* assume array is not behaved. */ + }; + Py_SET_TYPE(&arr_fields, &PyArray_Type); + Py_REFCNT(&arr_fields) = 1; + + if (NPY_UNLIKELY(descr->type_num == NPY_OBJECT)) { + /* + * We always have store objects directly, casting will lose some + * type information. Any other dtype discards the type information. + * TODO: For a Categorical[object] this path may be necessary? + */ + arr_fields.descr = descr; + return descr->f->setitem(value, item, &arr_fields); + } + + /* discover_dtype_from_pyobject includes a check for is_known_scalar_type */ + PyArray_DTypeMeta *DType = discover_dtype_from_pyobject( + value, NULL, NPY_DTYPE(descr)); + if (DType == NULL) { + return -1; + } + if (DType == NPY_DTYPE(descr) || DType == (PyArray_DTypeMeta *)Py_None) { + /* We can set the element directly (or at least will try to) */ + Py_XDECREF(DType); + arr_fields.descr = descr; + return descr->f->setitem(value, item, &arr_fields); + } + PyArray_Descr *tmp_descr; + tmp_descr = DType->discover_descr_from_pyobject(DType, value); + Py_DECREF(DType); + if (tmp_descr == NULL) { + return -1; + } + + char *data = PyObject_Malloc(tmp_descr->elsize); + if (data == NULL) { + PyErr_NoMemory(); + Py_DECREF(tmp_descr); + return -1; + } + if (PyDataType_FLAGCHK(tmp_descr, NPY_NEEDS_INIT)) { + memset(data, 0, tmp_descr->elsize); + } + arr_fields.descr = tmp_descr; + if (tmp_descr->f->setitem(value, data, &arr_fields) < 0) { + PyObject_Free(data); + Py_DECREF(tmp_descr); + return -1; + } + if (PyDataType_REFCHK(tmp_descr)) { + /* We could probably use move-references above */ + PyArray_Item_INCREF(data, tmp_descr); + } + + int res = 0; + int needs_api = 0; + PyArray_StridedUnaryOp *stransfer; + NpyAuxData *transferdata; + if (PyArray_GetDTypeTransferFunction( + 0, 0, 0, tmp_descr, descr, 0, &stransfer, &transferdata, + &needs_api) == NPY_FAIL) { + res = -1; + goto finish; + } + stransfer(item, 0, data, 0, 1, tmp_descr->elsize, transferdata); + NPY_AUXDATA_FREE(transferdata); + + if (needs_api && PyErr_Occurred()) { + res = -1; + } + + finish: + if (PyDataType_REFCHK(tmp_descr)) { + /* We could probably use move-references above */ + PyArray_Item_XDECREF(data, tmp_descr); + } + PyObject_Free(data); + Py_DECREF(tmp_descr); + return res; +} + + +static int +update_shape(int curr_ndim, int *max_ndim, + npy_intp out_shape[NPY_MAXDIMS], int new_ndim, + const npy_intp new_shape[NPY_MAXDIMS], npy_bool sequence, + enum _dtype_discovery_flags *flags) +{ + int success = 0; /* unsuccessful if array is ragged */ + const npy_bool max_dims_reached = *flags & MAX_DIMS_WAS_REACHED; + + if (curr_ndim + new_ndim > *max_ndim) { + success = -1; + /* Only update/check as many dims as possible, max_ndim is unchanged */ + new_ndim = *max_ndim - curr_ndim; + } + else if (!sequence && (*max_ndim != curr_ndim + new_ndim)) { + /* + * Sequences do not update max_ndim, otherwise shrink and check. + * This is depth first, so if it is already set, `out_shape` is filled. + */ + *max_ndim = curr_ndim + new_ndim; + /* If a shape was already set, this is also ragged */ + if (max_dims_reached) { + success = -1; + } + } + for (int i = 0; i < new_ndim; i++) { + npy_intp curr_dim = out_shape[curr_ndim + i]; + npy_intp new_dim = new_shape[i]; + + if (!max_dims_reached) { + out_shape[curr_ndim + i] = new_dim; + } + else if (new_dim != curr_dim) { + /* The array is ragged, and this dimension is unusable already */ + success = -1; + if (!sequence) { + /* Remove dimensions that we cannot use: */ + *max_ndim -= new_ndim + i; + } + else { + assert(i == 0); + /* max_ndim is usually not updated for sequences, so set now: */ + *max_ndim = curr_ndim; + } + break; + } + } + if (!sequence) { + *flags |= MAX_DIMS_WAS_REACHED; + } + return success; +} + + +#define COERCION_CACHE_CACHE_SIZE 5 +static int _coercion_cache_num = 0; +static coercion_cache_obj *_coercion_cache_cache[COERCION_CACHE_CACHE_SIZE]; + +/* + * Steals a reference to the object. + */ +static NPY_INLINE int +npy_new_coercion_cache( + PyObject *converted_obj, PyObject *arr_or_sequence, npy_bool sequence, + coercion_cache_obj ***next_ptr, int ndim) +{ + coercion_cache_obj *cache; + if (_coercion_cache_num > 0) { + _coercion_cache_num--; + cache = _coercion_cache_cache[_coercion_cache_num]; + } + else { + cache = PyObject_MALLOC(sizeof(coercion_cache_obj)); + } + if (cache == NULL) { + PyErr_NoMemory(); + return -1; + } + cache->converted_obj = converted_obj; + cache->arr_or_sequence = arr_or_sequence; + cache->sequence = sequence; + cache->depth = ndim; + cache->next = NULL; + **next_ptr = cache; + *next_ptr = &(cache->next); + return 0; +} + +/** + * Unlink coercion cache item. + * + * @param current + * @return next coercion cache object (or NULL) + */ +NPY_NO_EXPORT NPY_INLINE coercion_cache_obj * +npy_unlink_coercion_cache(coercion_cache_obj *current) +{ + coercion_cache_obj *next = current->next; + Py_DECREF(current->arr_or_sequence); + if (_coercion_cache_num < COERCION_CACHE_CACHE_SIZE) { + _coercion_cache_cache[_coercion_cache_num] = current; + _coercion_cache_num++; + } + else { + PyObject_FREE(current); + } + return next; +} + +NPY_NO_EXPORT NPY_INLINE void +npy_free_coercion_cache(coercion_cache_obj *next) { + /* We only need to check from the last used cache pos */ + while (next != NULL) { + next = npy_unlink_coercion_cache(next); + } +} + +#undef COERCION_CACHE_CACHE_SIZE + +/** + * Do the promotion step and possible casting. This function should + * never be called if a descriptor was requested. In that case the output + * dtype is not of importance, so we must not risk promotion errors. + * + * @param out_descr The current descriptor. + * @param descr The newly found descriptor to promote with + * @param flags dtype discover flags to signal failed promotion. + * @return -1 on error, 0 on success. + */ +static NPY_INLINE int +handle_promotion(PyArray_Descr **out_descr, PyArray_Descr *descr, + enum _dtype_discovery_flags *flags) +{ + assert(!(*flags & DESCRIPTOR_WAS_SET)); + + if (*out_descr == NULL) { + Py_INCREF(descr); + *out_descr = descr; + return 0; + } + PyArray_Descr *new_descr = PyArray_PromoteTypes(descr, *out_descr); + if (new_descr == NULL) { + PyErr_Clear(); + *flags |= PROMOTION_FAILED; + /* Continue with object, since we may need the dimensionality */ + new_descr = PyArray_DescrFromType(NPY_OBJECT); + } + Py_SETREF(*out_descr, new_descr); + return 0; +} + + +/** + * Handle a leave node (known scalar) during dtype and shape discovery. + * + * @param obj The python object or nested sequence to convert + * @param max_dims The maximum number of dimensions. + * @param curr_dims The current number of dimensions (depth in the recursion) + * @param out_shape The discovered output shape, will be filled + * @param coercion_cache The coercion cache object to use. + * @param DType the DType class that should be used, or NULL, if not provided. + * @param flags used signal that this is a ragged array, used internally and + * can be expanded if necessary. + */ +static NPY_INLINE int +handle_scalar( + PyObject *obj, int curr_dims, int *max_dims, + PyArray_Descr **out_descr, npy_intp *out_shape, + PyArray_DTypeMeta *fixed_DType, + enum _dtype_discovery_flags *flags, PyArray_DTypeMeta *DType) +{ + PyArray_Descr *descr; + + if (update_shape(curr_dims, max_dims, out_shape, + 0, NULL, NPY_FALSE, flags) < 0) { + *flags |= FOUND_RAGGED_ARRAY; + return *max_dims; + } + if (*flags & DESCRIPTOR_WAS_SET) { + /* no need to do any promotion */ + return *max_dims; + } + /* This is a scalar, so find the descriptor */ + descr = find_scalar_descriptor(fixed_DType, DType, obj); + if (descr == NULL) { + return -1; + } + if (handle_promotion(out_descr, descr, flags) < 0) { + Py_DECREF(descr); + return -1; + } + Py_DECREF(descr); + return *max_dims; +} + + +/** + * Return the correct descriptor given an array object and a DType class. + * + * This is identical to casting the arrays descriptor/dtype to the new + * DType class + * + * @param arr The array object. + * @param DType The DType class to cast to (or NULL for convenience) + * @param out_descr The output descriptor will set. The result can be NULL + * when the array is of object dtype and has no elements. + * + * @return -1 on failure, 0 on success. + */ +static int +find_descriptor_from_array( + PyArrayObject *arr, PyArray_DTypeMeta *DType, PyArray_Descr **out_descr) +{ + enum _dtype_discovery_flags flags = 0; + *out_descr = NULL; + + if (NPY_UNLIKELY(DType != NULL && DType->parametric && + PyArray_ISOBJECT(arr))) { + /* + * We have one special case, if (and only if) the input array is of + * object DType and the dtype is not fixed already but parametric. + * Then, we allow inspection of all elements, treating them as + * elements. We do this recursively, so nested 0-D arrays can work, + * but nested higher dimensional arrays will lead to an error. + */ + assert(DType->type_num != NPY_OBJECT); /* not parametric */ + + PyArrayIterObject *iter; + iter = (PyArrayIterObject *)PyArray_IterNew((PyObject *)arr); + if (iter == NULL) { + return -1; + } + while (iter->index < iter->size) { + PyArray_DTypeMeta *item_DType; + /* + * Note: If the array contains typed objects we may need to use + * the dtype to use casting for finding the correct instance. + */ + PyObject *elem = PyArray_GETITEM(arr, iter->dataptr); + if (elem == NULL) { + Py_DECREF(iter); + return -1; + } + item_DType = discover_dtype_from_pyobject(elem, &flags, DType); + if (item_DType == NULL) { + Py_DECREF(iter); + Py_DECREF(elem); + return -1; + } + if (item_DType == (PyArray_DTypeMeta *)Py_None) { + Py_SETREF(item_DType, NULL); + } + int flat_max_dims = 0; + if (handle_scalar(elem, 0, &flat_max_dims, out_descr, + NULL, DType, &flags, item_DType) < 0) { + Py_DECREF(iter); + Py_DECREF(elem); + Py_XDECREF(item_DType); + return -1; + } + Py_XDECREF(item_DType); + Py_DECREF(elem); + PyArray_ITER_NEXT(iter); + } + Py_DECREF(iter); + } + else if (DType != NULL && NPY_UNLIKELY(DType->type_num == NPY_DATETIME) && + PyArray_ISSTRING(arr)) { + /* + * TODO: This branch should be deprecated IMO, the workaround is + * to cast to the object to a string array. Although a specific + * function (if there is even any need) would be better. + * This is value based casting! + * Unless of course we actually want to support this kind of thing + * in general (not just for object dtype)... + */ + PyArray_DatetimeMetaData meta; + meta.base = NPY_FR_GENERIC; + meta.num = 1; + + if (find_string_array_datetime64_type(arr, &meta) < 0) { + return -1; + } + else { + *out_descr = create_datetime_dtype(NPY_DATETIME, &meta); + if (*out_descr == NULL) { + return -1; + } + } + } + else { + /* + * If this is not an object array figure out the dtype cast, + * or simply use the returned DType. + */ + *out_descr = cast_descriptor_to_fixed_dtype( + PyArray_DESCR(arr), DType); + if (*out_descr == NULL) { + return -1; + } + } + return 0; +} + +/** + * Given a dtype or DType object, find the correct descriptor to cast the + * array to. + * + * This function is identical to normal casting using only the dtype, however, + * it supports inspecting the elements when the array has object dtype + * (and the given datatype describes a parametric DType class). + * + * @param arr + * @param dtype A dtype instance or class. + * @return A concrete dtype instance or NULL + */ +NPY_NO_EXPORT PyArray_Descr * +PyArray_AdaptDescriptorToArray(PyArrayObject *arr, PyObject *dtype) +{ + /* If the requested dtype is flexible, adapt it */ + PyArray_Descr *new_dtype; + PyArray_DTypeMeta *new_DType; + int res; + + res = PyArray_ExtractDTypeAndDescriptor((PyObject *)dtype, + &new_dtype, &new_DType); + if (res < 0) { + return NULL; + } + if (new_dtype == NULL) { + res = find_descriptor_from_array(arr, new_DType, &new_dtype); + if (res < 0) { + Py_DECREF(new_DType); + return NULL; + } + if (new_dtype == NULL) { + /* This is an object array but contained no elements, use default */ + new_dtype = new_DType->default_descr(new_DType); + } + } + Py_DECREF(new_DType); + return new_dtype; +} + + +/** + * Recursion helper for `PyArray_DiscoverDTypeAndShape`. See its + * documentation for additional details. + * + * @param obj The current (possibly nested) object + * @param curr_dims The current depth, i.e. initially 0 and increasing. + * @param max_dims Maximum number of dimensions, modified during discovery. + * @param out_descr dtype instance (or NULL) to promoted and update. + * @param out_shape The current shape (updated) + * @param coercion_cache_tail_ptr The tail of the linked list of coercion + * cache objects, which hold on to converted sequences and arrays. + * This is a pointer to the `->next` slot of the previous cache so + * that we can append a new cache object (and update this pointer). + * (Initially it is a pointer to the user-provided head pointer). + * @param fixed_DType User provided fixed DType class + * @param flags Discovery flags (reporting and behaviour flags, see def.) + * @return The updated number of maximum dimensions (i.e. scalars will set + * this to the current dimensions). + */ +NPY_NO_EXPORT int +PyArray_DiscoverDTypeAndShape_Recursive( + PyObject *obj, int curr_dims, int max_dims, PyArray_Descr**out_descr, + npy_intp out_shape[NPY_MAXDIMS], + coercion_cache_obj ***coercion_cache_tail_ptr, + PyArray_DTypeMeta *fixed_DType, enum _dtype_discovery_flags *flags) +{ + PyArrayObject *arr = NULL; + PyObject *seq; + + /* + * The first step is to find the DType class if it was not provided, + * alternatively we have to find out that this is not a scalar at all + * (which could fail and lead us to `object` dtype). + */ + PyArray_DTypeMeta *DType = NULL; + + if (NPY_UNLIKELY(*flags & DISCOVER_STRINGS_AS_SEQUENCES)) { + /* + * We currently support that bytes/strings are considered sequences, + * if the dtype is np.dtype('c'), this should be deprecated probably, + * but requires hacks right now. + */ + if (PyBytes_Check(obj) && PyBytes_Size(obj) != 1) { + goto force_sequence_due_to_char_dtype; + } + else if (PyUnicode_Check(obj) && PyUnicode_GetLength(obj) != 1) { + goto force_sequence_due_to_char_dtype; + } + } + + /* If this is a known scalar, find the corresponding DType class */ + DType = discover_dtype_from_pyobject(obj, flags, fixed_DType); + if (DType == NULL) { + return -1; + } + else if (DType == (PyArray_DTypeMeta *)Py_None) { + Py_DECREF(Py_None); + } + else { + max_dims = handle_scalar( + obj, curr_dims, &max_dims, out_descr, out_shape, fixed_DType, + flags, DType); + Py_DECREF(DType); + return max_dims; + } + + /* + * At this point we expect to find either a sequence, or an array-like. + * Although it is still possible that this fails and we have to use + * `object`. + */ + if (PyArray_Check(obj)) { + arr = (PyArrayObject *)obj; + Py_INCREF(arr); + } + else { + PyArray_Descr *requested_descr = NULL; + if (*flags & DESCRIPTOR_WAS_SET) { + /* __array__ may be passed the requested descriptor if provided */ + requested_descr = *out_descr; + } + arr = (PyArrayObject *)_array_from_array_like(obj, + requested_descr, 0, NULL); + if (arr == NULL) { + return -1; + } + else if (arr == (PyArrayObject *)Py_NotImplemented) { + Py_DECREF(arr); + arr = NULL; + } + } + if (arr != NULL) { + /* + * This is an array object which will be added to the cache, keeps + * the reference to the array alive (takes ownership). + */ + if (npy_new_coercion_cache(obj, (PyObject *)arr, + 0, coercion_cache_tail_ptr, curr_dims) < 0) { + return -1; + } + + if (curr_dims == 0) { + /* + * Special case for reverse broadcasting, ignore max_dims if this + * is a single array-like object; needed for PyArray_CopyObject. + */ + memcpy(out_shape, PyArray_SHAPE(arr), + PyArray_NDIM(arr) * sizeof(npy_intp)); + max_dims = PyArray_NDIM(arr); + } + else if (update_shape(curr_dims, &max_dims, out_shape, + PyArray_NDIM(arr), PyArray_SHAPE(arr), NPY_FALSE, flags) < 0) { + *flags |= FOUND_RAGGED_ARRAY; + return max_dims; + } + + if (*flags & DESCRIPTOR_WAS_SET) { + return max_dims; + } + /* + * For arrays we may not just need to cast the dtype to the user + * provided fixed_DType. If this is an object array, the elements + * may need to be inspected individually. + * Note, this finds the descriptor of the array first and only then + * promotes here (different associativity). + */ + PyArray_Descr *cast_descr; + if (find_descriptor_from_array(arr, fixed_DType, &cast_descr) < 0) { + return -1; + } + if (cast_descr == NULL) { + /* object array with no elements, no need to promote/adjust. */ + return max_dims; + } + if (handle_promotion(out_descr, cast_descr, flags) < 0) { + Py_DECREF(cast_descr); + return -1; + } + Py_DECREF(cast_descr); + return max_dims; + } + + /* + * The last step is to assume the input should be handled as a sequence + * and to handle it recursively. That is, unless we have hit the + * dimension limit. + */ + npy_bool is_sequence = (PySequence_Check(obj) && PySequence_Size(obj) >= 0); + if (NPY_UNLIKELY(*flags & DISCOVER_TUPLES_AS_ELEMENTS) && + PyTuple_Check(obj)) { + is_sequence = NPY_FALSE; + } + if (curr_dims == max_dims || !is_sequence) { + /* Clear any PySequence_Size error which would corrupts further calls */ + PyErr_Clear(); + max_dims = handle_scalar( + obj, curr_dims, &max_dims, out_descr, out_shape, fixed_DType, + flags, NULL); + if (is_sequence) { + /* Flag as ragged or too deep array */ + *flags |= FOUND_RAGGED_ARRAY; + } + return max_dims; + } + /* If we stop supporting bytes/str subclasses, more may be required here: */ + assert(!PyBytes_Check(obj) && !PyUnicode_Check(obj)); + + force_sequence_due_to_char_dtype: + + /* Ensure we have a sequence (required for PyPy) */ + seq = PySequence_Fast(obj, "Could not convert object to sequence"); + if (seq == NULL) { + /* + * Specifically do not fail on things that look like a dictionary, + * instead treat them as scalar. + */ + if (PyErr_ExceptionMatches(PyExc_KeyError)) { + PyErr_Clear(); + max_dims = handle_scalar( + obj, curr_dims, &max_dims, out_descr, out_shape, fixed_DType, + flags, NULL); + return max_dims; + } + return -1; + } + /* The cache takes ownership of the sequence here. */ + if (npy_new_coercion_cache(obj, seq, 1, coercion_cache_tail_ptr, curr_dims) < 0) { + return -1; + } + + npy_intp size = PySequence_Fast_GET_SIZE(seq); + PyObject **objects = PySequence_Fast_ITEMS(seq); + + if (update_shape(curr_dims, &max_dims, + out_shape, 1, &size, NPY_TRUE, flags) < 0) { + /* But do update, if there this is a ragged case */ + *flags |= FOUND_RAGGED_ARRAY; + return max_dims; + } + if (size == 0) { + /* If the sequence is empty, this must be the last dimension */ + *flags |= MAX_DIMS_WAS_REACHED; + return curr_dims + 1; + } + + /* Recursive call for each sequence item */ + for (Py_ssize_t i = 0; i < size; i++) { + max_dims = PyArray_DiscoverDTypeAndShape_Recursive( + objects[i], curr_dims + 1, max_dims, + out_descr, out_shape, coercion_cache_tail_ptr, fixed_DType, + flags); + + if (max_dims < 0) { + return -1; + } + } + return max_dims; +} + + +/** + * Finds the DType and shape of an arbitrary nested sequence. This is the + * general purpose function to find the parameters of the array (but not + * the array itself) as returned by `np.array()` + * + * Note: Before considering to make part of this public, we should consider + * whether things such as `out_descr != NULL` should be supported in + * a public API. + * + * @param obj Scalar or nested sequences. + * @param max_dims Maximum number of dimensions (after this scalars are forced) + * @param out_shape Will be filled with the output shape (more than the actual + * shape may be written). + * @param coercion_cache NULL initialized reference to a cache pointer. + * May be set to the first coercion_cache, and has to be freed using + * npy_free_coercion_cache. + * This should be stored in a thread-safe manner (i.e. function static) + * and is designed to be consumed by `PyArray_AssignFromCache`. + * If not consumed, must be freed using `npy_free_coercion_cache`. + * @param fixed_DType A user provided fixed DType class. + * @param requested_descr A user provided fixed descriptor. This is always + * returned as the discovered descriptor, but currently only used + * for the ``__array__`` protocol. + * @param out_descr Set to the discovered output descriptor. This may be + * non NULL but only when fixed_DType/requested_descr are not given. + * If non NULL, it is the first dtype being promoted and used if there + * are no elements. + * The result may be unchanged (remain NULL) when converting a + * sequence with no elements. In this case it is callers responsibility + * to choose a default. + * @return dimensions of the discovered object or -1 on error. + * WARNING: If (and only if) the output is a single array, the ndim + * returned _can_ exceed the maximum allowed number of dimensions. + * It might be nice to deprecate this? But it allows things such as + * `arr1d[...] = np.array([[1,2,3,4]])` + */ +NPY_NO_EXPORT int +PyArray_DiscoverDTypeAndShape( + PyObject *obj, int max_dims, + npy_intp out_shape[NPY_MAXDIMS], + coercion_cache_obj **coercion_cache, + PyArray_DTypeMeta *fixed_DType, PyArray_Descr *requested_descr, + PyArray_Descr **out_descr) +{ + coercion_cache_obj **coercion_cache_head = coercion_cache; + *coercion_cache = NULL; + enum _dtype_discovery_flags flags = 0; + + /* + * Support a passed in descriptor (but only if nothing was specified). + */ + assert(*out_descr == NULL || fixed_DType == NULL); + /* Validate input of requested descriptor and DType */ + if (fixed_DType != NULL) { + assert(PyObject_TypeCheck( + (PyObject *)fixed_DType, (PyTypeObject *)&PyArrayDTypeMeta_Type)); + } + + if (requested_descr != NULL) { + assert(fixed_DType == NPY_DTYPE(requested_descr)); + /* The output descriptor must be the input. */ + Py_INCREF(requested_descr); + *out_descr = requested_descr; + flags |= DESCRIPTOR_WAS_SET; + } + + /* + * Call the recursive function, the setup for this may need expanding + * to handle caching better. + */ + + /* Legacy discovery flags */ + if (requested_descr != NULL) { + if (requested_descr->type_num == NPY_STRING && + requested_descr->type == 'c') { + /* Character dtype variation of string (should be deprecated...) */ + flags |= DISCOVER_STRINGS_AS_SEQUENCES; + } + else if (requested_descr->type_num == NPY_VOID && + (requested_descr->names || requested_descr->subarray)) { + /* Void is a chimera, in that it may or may not be structured... */ + flags |= DISCOVER_TUPLES_AS_ELEMENTS; + } + } + + int ndim = PyArray_DiscoverDTypeAndShape_Recursive( + obj, 0, max_dims, out_descr, out_shape, &coercion_cache, + fixed_DType, &flags); + if (ndim < 0) { + goto fail; + } + + if (NPY_UNLIKELY(flags & FOUND_RAGGED_ARRAY)) { + /* + * If max-dims was reached and the dimensions reduced, this is ragged. + * Otherwise, we merely reached the maximum dimensions, which is + * slightly different. This happens for example for `[1, [2, 3]]` + * where the maximum dimensions is 1, but then a sequence found. + * + * In this case we need to inform the user and clean out the cache + * since it may be too deep. + */ + + /* Handle reaching the maximum depth differently: */ + int too_deep = ndim == max_dims; + + if (fixed_DType == NULL) { + /* This is discovered as object, but deprecated */ + static PyObject *visibleDeprecationWarning = NULL; + npy_cache_import( + "numpy", "VisibleDeprecationWarning", + &visibleDeprecationWarning); + if (visibleDeprecationWarning == NULL) { + goto fail; + } + if (!too_deep) { + /* NumPy 1.19, 2019-11-01 */ + if (PyErr_WarnEx(visibleDeprecationWarning, + "Creating an ndarray from ragged nested sequences (which " + "is a list-or-tuple of lists-or-tuples-or ndarrays with " + "different lengths or shapes) is deprecated. If you " + "meant to do this, you must specify 'dtype=object' " + "when creating the ndarray.", 1) < 0) { + goto fail; + } + } + else { + /* NumPy 1.20, 2020-05-08 */ + /* Note, max_dims should normally always be NPY_MAXDIMS here */ + if (PyErr_WarnFormat(visibleDeprecationWarning, 1, + "Creating an ndarray from nested sequences exceeding " + "the maximum number of dimensions of %d is deprecated. " + "If you mean to do this, you must specify " + "'dtype=object' when creating the ndarray.", + max_dims) < 0) { + goto fail; + } + } + /* Ensure that ragged arrays always return object dtype */ + Py_XSETREF(*out_descr, PyArray_DescrFromType(NPY_OBJECT)); + } + else if (fixed_DType->type_num != NPY_OBJECT) { + /* Only object DType supports ragged cases unify error */ + if (!too_deep) { + PyObject *shape = PyArray_IntTupleFromIntp(ndim, out_shape); + PyErr_Format(PyExc_ValueError, + "setting an array element with a sequence. The " + "requested array has an inhomogeneous shape after " + "%d dimensions. The detected shape was " + "%R + inhomogeneous part.", + ndim, shape); + Py_DECREF(shape); + goto fail; + } + else { + PyErr_Format(PyExc_ValueError, + "setting an array element with a sequence. The " + "requested array would exceed the maximum number of " + "dimension of %d.", + max_dims); + goto fail; + } + } + + /* + * If the array is ragged, the cache may be too deep, so clean it. + * The cache is left at the same depth as the array though. + */ + coercion_cache_obj **next_ptr = coercion_cache_head; + coercion_cache_obj *current = *coercion_cache_head; /* item to check */ + while (current != NULL) { + if (current->depth > ndim) { + /* delete "next" cache item and advanced it (unlike later) */ + current = npy_unlink_coercion_cache(current); + continue; + } + /* advance both prev and next, and set prev->next to new item */ + *next_ptr = current; + next_ptr = &(current->next); + current = current->next; + } + *next_ptr = NULL; + } + /* We could check here for max-ndims being reached as well */ + + if (requested_descr != NULL) { + /* descriptor was provided, we did not accidentally change it */ + assert(*out_descr == requested_descr); + } + else if (NPY_UNLIKELY(*out_descr == NULL)) { + /* + * When the object contained no elements (sequence of length zero), + * the no descriptor may have been found. When a DType was requested + * we use it to define the output dtype. + * Otherwise, out_descr will remain NULL and the caller has to set + * the correct default. + */ + if (fixed_DType != NULL) { + if (fixed_DType->default_descr == NULL) { + Py_INCREF(fixed_DType->singleton); + *out_descr = fixed_DType->singleton; + } + else { + *out_descr = fixed_DType->default_descr(fixed_DType); + if (*out_descr == NULL) { + goto fail; + } + } + } + } + return ndim; + + fail: + npy_free_coercion_cache(*coercion_cache_head); + *coercion_cache_head = NULL; + Py_XSETREF(*out_descr, NULL); + return -1; +} + + + +/** + * Check the descriptor is a legacy "flexible" DType instance, this is + * an instance which is (normally) not attached to an array, such as a string + * of length 0 or a datetime with no unit. + * These should be largely deprecated, and represent only the DType class + * for most `dtype` parameters. + * + * TODO: This function should eventually recieve a deprecation warning and + * be removed. + * + * @param descr + * @return 1 if this is not a concrete dtype instance 0 otherwise + */ +static int +descr_is_legacy_parametric_instance(PyArray_Descr *descr) +{ + if (PyDataType_ISUNSIZED(descr)) { + return 1; + } + /* Flexible descr with generic time unit (which can be adapted) */ + if (PyDataType_ISDATETIME(descr)) { + PyArray_DatetimeMetaData *meta; + meta = get_datetime_metadata_from_dtype(descr); + if (meta->base == NPY_FR_GENERIC) { + return 1; + } + } + return 0; +} + + +/** + * Given either a DType instance or class, (or legacy flexible instance), + * ands sets output dtype instance and DType class. Both results may be + * NULL, but if `out_descr` is set `out_DType` will always be the + * corresponding class. + * + * @param dtype + * @param out_descr + * @param out_DType + * @return 0 on success -1 on failure + */ +NPY_NO_EXPORT int +PyArray_ExtractDTypeAndDescriptor(PyObject *dtype, + PyArray_Descr **out_descr, PyArray_DTypeMeta **out_DType) +{ + *out_DType = NULL; + *out_descr = NULL; + + if (dtype != NULL) { + if (PyObject_TypeCheck(dtype, (PyTypeObject *)&PyArrayDTypeMeta_Type)) { + assert(dtype != (PyObject * )&PyArrayDescr_Type); /* not np.dtype */ + *out_DType = (PyArray_DTypeMeta *)dtype; + Py_INCREF(*out_DType); + } + else if (PyObject_TypeCheck((PyObject *)Py_TYPE(dtype), + (PyTypeObject *)&PyArrayDTypeMeta_Type)) { + *out_DType = NPY_DTYPE(dtype); + Py_INCREF(*out_DType); + if (!descr_is_legacy_parametric_instance((PyArray_Descr *)dtype)) { + *out_descr = (PyArray_Descr *)dtype; + Py_INCREF(*out_descr); + } + } + else { + PyErr_SetString(PyExc_TypeError, + "dtype parameter must be a DType instance or class."); + return -1; + } + } + return 0; +} + + +/* + * Python API function to expose the dtype+shape discovery functionality + * directly. + */ +NPY_NO_EXPORT PyObject * +_discover_array_parameters(PyObject *NPY_UNUSED(self), + PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"obj", "dtype", NULL}; + + PyObject *obj; + PyObject *dtype = NULL; + PyArray_Descr *fixed_descriptor = NULL; + PyArray_DTypeMeta *fixed_DType = NULL; + npy_intp shape[NPY_MAXDIMS]; + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "O|O:_discover_array_parameters", kwlist, + &obj, &dtype)) { + return NULL; + } + + if (PyArray_ExtractDTypeAndDescriptor(dtype, + &fixed_descriptor, &fixed_DType) < 0) { + return NULL; + } + + coercion_cache_obj *coercion_cache = NULL; + PyObject *out_dtype = NULL; + int ndim = PyArray_DiscoverDTypeAndShape( + obj, NPY_MAXDIMS, shape, + &coercion_cache, + fixed_DType, fixed_descriptor, (PyArray_Descr **)&out_dtype); + Py_XDECREF(fixed_DType); + Py_XDECREF(fixed_descriptor); + if (ndim < 0) { + return NULL; + } + npy_free_coercion_cache(coercion_cache); + if (out_dtype == NULL) { + /* Empty sequence, report this as None. */ + out_dtype = Py_None; + Py_INCREF(Py_None); + } + + PyObject *shape_tuple = PyArray_IntTupleFromIntp(ndim, shape); + if (shape_tuple == NULL) { + return NULL; + } + + PyObject *res = PyTuple_Pack(2, (PyObject *)out_dtype, shape_tuple); + Py_DECREF(out_dtype); + Py_DECREF(shape_tuple); + return res; +} diff --git a/numpy/core/src/multiarray/array_coercion.h b/numpy/core/src/multiarray/array_coercion.h new file mode 100644 index 000000000..90ce0355a --- /dev/null +++ b/numpy/core/src/multiarray/array_coercion.h @@ -0,0 +1,58 @@ +#ifndef _NPY_ARRAY_COERCION_H +#define _NPY_ARRAY_COERCION_H + + +/* + * We do not want to coerce arrays many times unless absolutely necessary. + * The same goes for sequences, so everything we have seen, we will have + * to store somehow. This is a linked list of these objects. + */ +typedef struct coercion_cache_obj { + PyObject *converted_obj; + PyObject *arr_or_sequence; + struct coercion_cache_obj *next; + npy_bool sequence; + int depth; /* the dimension at which this object was found. */ +} coercion_cache_obj; + + +NPY_NO_EXPORT int +_PyArray_MapPyTypeToDType( + PyArray_DTypeMeta *DType, PyTypeObject *pytype, npy_bool userdef); + +NPY_NO_EXPORT int +PyArray_Pack(PyArray_Descr *descr, char *item, PyObject *value); + +NPY_NO_EXPORT PyArray_Descr * +PyArray_AdaptDescriptorToArray(PyArrayObject *arr, PyObject *dtype); + +NPY_NO_EXPORT int +PyArray_DiscoverDTypeAndShape( + PyObject *obj, int max_dims, + npy_intp out_shape[NPY_MAXDIMS], + coercion_cache_obj **coercion_cache, + PyArray_DTypeMeta *fixed_DType, PyArray_Descr *requested_descr, + PyArray_Descr **out_descr); + +NPY_NO_EXPORT int +PyArray_ExtractDTypeAndDescriptor(PyObject *dtype, + PyArray_Descr **out_descr, PyArray_DTypeMeta **out_DType); + +NPY_NO_EXPORT PyObject * +_discover_array_parameters(PyObject *NPY_UNUSED(self), + PyObject *args, PyObject *kwargs); + + +/* Would make sense to inline the freeing functions everywhere */ +/* Frees the coercion cache object recursively. */ +NPY_NO_EXPORT void +npy_free_coercion_cache(coercion_cache_obj *first); + +/* unlink a single item and return the next */ +NPY_NO_EXPORT coercion_cache_obj * +npy_unlink_coercion_cache(coercion_cache_obj *current); + +NPY_NO_EXPORT int +PyArray_AssignFromCache(PyArrayObject *self, coercion_cache_obj *cache); + +#endif /* _NPY_ARRAY_COERCION_H */ diff --git a/numpy/core/src/multiarray/arrayobject.c b/numpy/core/src/multiarray/arrayobject.c index dedaf38eb..95c650674 100644 --- a/numpy/core/src/multiarray/arrayobject.c +++ b/numpy/core/src/multiarray/arrayobject.c @@ -43,6 +43,7 @@ maintainer email: oliphant.travis@ieee.org #include "arrayobject.h" #include "conversion_utils.h" #include "ctors.h" +#include "dtypemeta.h" #include "methods.h" #include "descriptor.h" #include "iterators.h" @@ -57,6 +58,7 @@ maintainer email: oliphant.travis@ieee.org #include "strfuncs.h" #include "binop_override.h" +#include "array_coercion.h" /*NUMPY_API Compute the size of an array (in number of items) @@ -235,136 +237,96 @@ PyArray_SetBaseObject(PyArrayObject *arr, PyObject *obj) } +/** + * Assign an arbitrary object a NumPy array. This is largely basically + * identical to PyArray_FromAny, but assigns directly to the output array. + * + * @param dest Array to be written to + * @param src_object Object to be assigned, array-coercion rules apply. + * @return 0 on success -1 on failures. + */ /*NUMPY_API*/ NPY_NO_EXPORT int PyArray_CopyObject(PyArrayObject *dest, PyObject *src_object) { int ret = 0; - PyArrayObject *src; + PyArrayObject *view; PyArray_Descr *dtype = NULL; - int ndim = 0; + int ndim; npy_intp dims[NPY_MAXDIMS]; + coercion_cache_obj *cache = NULL; - Py_INCREF(src_object); /* - * Special code to mimic Numeric behavior for - * character arrays. + * We have to set the maximum number of dimensions here to support + * sequences within object arrays. */ - if (PyArray_DESCR(dest)->type == NPY_CHARLTR && - PyArray_NDIM(dest) > 0 && - PyString_Check(src_object)) { - npy_intp n_new, n_old; - char *new_string; - PyObject *tmp; + ndim = PyArray_DiscoverDTypeAndShape(src_object, + PyArray_NDIM(dest), dims, &cache, + NPY_DTYPE(PyArray_DESCR(dest)), PyArray_DESCR(dest), &dtype); + if (ndim < 0) { + return -1; + } - n_new = PyArray_DIMS(dest)[PyArray_NDIM(dest)-1]; - n_old = PyString_Size(src_object); - if (n_new > n_old) { - new_string = malloc(n_new); - if (new_string == NULL) { - Py_DECREF(src_object); - PyErr_NoMemory(); - return -1; - } - memcpy(new_string, PyString_AS_STRING(src_object), n_old); - memset(new_string + n_old, ' ', n_new - n_old); - tmp = PyString_FromStringAndSize(new_string, n_new); - free(new_string); - Py_DECREF(src_object); - src_object = tmp; - } + if (cache != NULL && !(cache->sequence)) { + /* The input is an array or array object, so assign directly */ + assert(cache->converted_obj == src_object); + view = (PyArrayObject *)cache->arr_or_sequence; + Py_DECREF(dtype); + ret = PyArray_AssignArray(dest, view, NULL, NPY_UNSAFE_CASTING); + npy_free_coercion_cache(cache); + return ret; } /* - * Get either an array object we can copy from, or its parameters - * if there isn't a convenient array available. + * We may need to broadcast, due to shape mismatches, in this case + * create a temporary array first, and assign that after filling + * it from the sequences/scalar. */ - if (PyArray_GetArrayParamsFromObject_int(src_object, - PyArray_DESCR(dest), 0, &dtype, &ndim, dims, &src) < 0) { - Py_DECREF(src_object); - return -1; + if (ndim != PyArray_NDIM(dest) || + !PyArray_CompareLists(PyArray_DIMS(dest), dims, ndim)) { + /* + * Broadcasting may be necessary, so assign to a view first. + * This branch could lead to a shape mismatch error later. + */ + assert (ndim <= PyArray_NDIM(dest)); /* would error during discovery */ + view = (PyArrayObject *) PyArray_NewFromDescr( + &PyArray_Type, dtype, ndim, dims, NULL, NULL, + PyArray_FLAGS(dest) & NPY_ARRAY_F_CONTIGUOUS, NULL); + if (view == NULL) { + npy_free_coercion_cache(cache); + return -1; + } + } + else { + Py_DECREF(dtype); + view = dest; } - /* If it's not an array, either assign from a sequence or as a scalar */ - if (src == NULL) { - /* If the input is scalar */ - if (ndim == 0) { - /* If there's one dest element and src is a Python scalar */ - if (PyArray_IsScalar(src_object, Generic)) { - char *value; - int retcode; - - value = scalar_value(src_object, dtype); - if (value == NULL) { - Py_DECREF(dtype); - Py_DECREF(src_object); - return -1; - } - - /* TODO: switch to SAME_KIND casting */ - retcode = PyArray_AssignRawScalar(dest, dtype, value, - NULL, NPY_UNSAFE_CASTING); - Py_DECREF(dtype); - Py_DECREF(src_object); - return retcode; - } - /* Otherwise use the dtype's setitem function */ - else { - if (PyArray_SIZE(dest) == 1) { - Py_DECREF(dtype); - Py_DECREF(src_object); - ret = PyArray_SETITEM(dest, PyArray_DATA(dest), src_object); - return ret; - } - else { - src = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, - dtype, 0, NULL, NULL, - NULL, 0, NULL); - if (src == NULL) { - Py_DECREF(src_object); - return -1; - } - if (PyArray_SETITEM(src, PyArray_DATA(src), src_object) < 0) { - Py_DECREF(src_object); - Py_DECREF(src); - return -1; - } - } - } + /* Assign the values to `view` (whichever array that is) */ + if (cache == NULL) { + /* single (non-array) item, assign immediately */ + if (PyArray_Pack( + PyArray_DESCR(view), PyArray_DATA(view), src_object) < 0) { + goto fail; } - else { - /* - * If there are more than enough dims, use AssignFromSequence - * because it can handle this style of broadcasting. - */ - if (ndim >= PyArray_NDIM(dest)) { - int res; - Py_DECREF(dtype); - res = PyArray_AssignFromSequence(dest, src_object); - Py_DECREF(src_object); - return res; - } - /* Otherwise convert to an array and do an array-based copy */ - src = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, - dtype, ndim, dims, NULL, NULL, - PyArray_ISFORTRAN(dest), NULL); - if (src == NULL) { - Py_DECREF(src_object); - return -1; - } - if (PyArray_AssignFromSequence(src, src_object) < 0) { - Py_DECREF(src); - Py_DECREF(src_object); - return -1; - } + } + else { + if (PyArray_AssignFromCache(view, cache) < 0) { + goto fail; } } - - /* If it's an array, do a move (handling possible overlapping data) */ - ret = PyArray_MoveInto(dest, src); - Py_DECREF(src); - Py_DECREF(src_object); + if (view == dest) { + return 0; + } + ret = PyArray_AssignArray(dest, view, NULL, NPY_UNSAFE_CASTING); + Py_DECREF(view); return ret; + + fail: + if (view != dest) { + Py_DECREF(view); + } + return -1; } diff --git a/numpy/core/src/multiarray/buffer.c b/numpy/core/src/multiarray/buffer.c index 232176011..8b482dc03 100644 --- a/numpy/core/src/multiarray/buffer.c +++ b/numpy/core/src/multiarray/buffer.c @@ -64,7 +64,7 @@ _append_char(_tmp_string_t *s, char c) char *p; size_t to_alloc = (s->allocated == 0) ? INIT_SIZE : (2 * s->allocated); - p = realloc(s->s, to_alloc); + p = PyObject_Realloc(s->s, to_alloc); if (p == NULL) { PyErr_SetString(PyExc_MemoryError, "memory allocation failed"); return -1; @@ -135,12 +135,25 @@ fail: * AND, the descr element size is a multiple of the alignment, * AND, the array data is positioned to alignment granularity. */ -static int +static NPY_INLINE int _is_natively_aligned_at(PyArray_Descr *descr, PyArrayObject *arr, Py_ssize_t offset) { int k; + if (NPY_LIKELY(descr == PyArray_DESCR(arr))) { + /* + * If the descriptor is the arrays descriptor we can assume the + * array's alignment is correct. + */ + assert(offset == 0); + if (PyArray_ISALIGNED(arr)) { + assert(descr->elsize % descr->alignment == 0); + return 1; + } + return 0; + } + if ((Py_ssize_t)(PyArray_DATA(arr)) % descr->alignment != 0) { return 0; } @@ -297,8 +310,6 @@ _buffer_format_string(PyArray_Descr *descr, _tmp_string_t *str, descr->type_num == NPY_ULONGLONG); } - *offset += descr->elsize; - if (PyArray_IsScalar(obj, Generic)) { /* scalars are always natively aligned */ is_natively_aligned = 1; @@ -308,6 +319,8 @@ _buffer_format_string(PyArray_Descr *descr, _tmp_string_t *str, (PyArrayObject*)obj, *offset); } + *offset += descr->elsize; + if (descr->byteorder == '=' && is_natively_aligned) { /* Prefer native types, to cater for Cython */ is_standard_size = 0; @@ -445,49 +458,22 @@ static PyObject *_buffer_info_cache = NULL; static _buffer_info_t* _buffer_info_new(PyObject *obj) { + /* + * Note that the buffer info is cached as PyLongObjects making them appear + * like unreachable lost memory to valgrind. + */ _buffer_info_t *info; _tmp_string_t fmt = {NULL, 0, 0}; int k; PyArray_Descr *descr = NULL; int err = 0; - /* - * Note that the buffer info is cached as pyints making them appear like - * unreachable lost memory to valgrind. - */ - info = malloc(sizeof(_buffer_info_t)); - if (info == NULL) { - PyErr_NoMemory(); - goto fail; - } - - if (PyArray_IsScalar(obj, Datetime) || PyArray_IsScalar(obj, Timedelta)) { - /* - * Special case datetime64 scalars to remain backward compatible. - * This will change in a future version. - * Note arrays of datetime64 and structured arrays with datetime64 - * fields will not hit this code path and are currently unsupported - * in _buffer_format_string. - */ - if (_append_char(&fmt, 'B') < 0) { - goto fail; - } - if (_append_char(&fmt, '\0') < 0) { - goto fail; - } - info->ndim = 1; - info->shape = malloc(sizeof(Py_ssize_t) * 2); - if (info->shape == NULL) { + if (PyArray_IsScalar(obj, Void)) { + info = PyObject_Malloc(sizeof(_buffer_info_t)); + if (info == NULL) { PyErr_NoMemory(); goto fail; } - info->strides = info->shape + info->ndim; - info->shape[0] = 8; - info->strides[0] = 1; - info->format = fmt.s; - return info; - } - else if (PyArray_IsScalar(obj, Generic)) { descr = PyArray_DescrFromScalar(obj); if (descr == NULL) { goto fail; @@ -497,8 +483,16 @@ _buffer_info_new(PyObject *obj) info->strides = NULL; } else { + assert(PyArray_Check(obj)); PyArrayObject * arr = (PyArrayObject *)obj; descr = PyArray_DESCR(arr); + + info = PyObject_Malloc(sizeof(_buffer_info_t) + + sizeof(Py_ssize_t) * PyArray_NDIM(arr) * 2); + if (info == NULL) { + PyErr_NoMemory(); + goto fail; + } /* Fill in shape and strides */ info->ndim = PyArray_NDIM(arr); @@ -507,11 +501,8 @@ _buffer_info_new(PyObject *obj) info->strides = NULL; } else { - info->shape = malloc(sizeof(Py_ssize_t) * PyArray_NDIM(arr) * 2 + 1); - if (info->shape == NULL) { - PyErr_NoMemory(); - goto fail; - } + info->shape = (npy_intp *)((char *)info + sizeof(_buffer_info_t)); + assert((size_t)info->shape % sizeof(npy_intp) == 0); info->strides = info->shape + PyArray_NDIM(arr); for (k = 0; k < PyArray_NDIM(arr); ++k) { info->shape[k] = PyArray_DIMS(arr)[k]; @@ -525,11 +516,9 @@ _buffer_info_new(PyObject *obj) err = _buffer_format_string(descr, &fmt, obj, NULL, NULL); Py_DECREF(descr); if (err != 0) { - free(info->shape); goto fail; } if (_append_char(&fmt, '\0') < 0) { - free(info->shape); goto fail; } info->format = fmt.s; @@ -537,8 +526,8 @@ _buffer_info_new(PyObject *obj) return info; fail: - free(fmt.s); - free(info); + PyObject_Free(fmt.s); + PyObject_Free(info); return NULL; } @@ -569,12 +558,9 @@ static void _buffer_info_free(_buffer_info_t *info) { if (info->format) { - free(info->format); - } - if (info->shape) { - free(info->shape); + PyObject_Free(info->format); } - free(info); + PyObject_Free(info); } /* Get buffer info from the global dictionary */ diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index 55ae73779..2abc79167 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -9,6 +9,7 @@ #include "npy_pycompat.h" #include "common.h" +#include "abstractdtypes.h" #include "usertypes.h" #include "common.h" @@ -16,6 +17,7 @@ #include "get_attr_string.h" #include "mem_overlap.h" +#include "array_coercion.h" /* * The casting to use for implicit assignment operations resulting from @@ -44,88 +46,19 @@ _array_find_python_scalar_type(PyObject *op) else if (PyComplex_Check(op)) { return PyArray_DescrFromType(NPY_CDOUBLE); } - else if (PyInt_Check(op)) { - /* bools are a subclass of int */ - if (PyBool_Check(op)) { - return PyArray_DescrFromType(NPY_BOOL); - } - else { - return PyArray_DescrFromType(NPY_LONG); - } - } else if (PyLong_Check(op)) { - /* check to see if integer can fit into a longlong or ulonglong - and return that --- otherwise return object */ - if ((PyLong_AsLongLong(op) == -1) && PyErr_Occurred()) { - PyErr_Clear(); - } - else { - return PyArray_DescrFromType(NPY_LONGLONG); - } - - if ((PyLong_AsUnsignedLongLong(op) == (unsigned long long) -1) - && PyErr_Occurred()){ - PyErr_Clear(); - } - else { - return PyArray_DescrFromType(NPY_ULONGLONG); - } - - return PyArray_DescrFromType(NPY_OBJECT); + return PyArray_PyIntAbstractDType.discover_descr_from_pyobject( + &PyArray_PyIntAbstractDType, op); } return NULL; } -/* - * These constants are used to signal that the recursive dtype determination in - * PyArray_DTypeFromObject encountered a string type, and that the recursive - * search must be restarted so that string representation lengths can be - * computed for all scalar types. - */ -#define RETRY_WITH_STRING 1 -#define RETRY_WITH_UNICODE 2 - -/* - * Recursively examines the object to determine an appropriate dtype - * to use for converting to an ndarray. - * - * 'obj' is the object to be converted to an ndarray. - * - * 'maxdims' is the maximum recursion depth. - * - * 'out_dtype' should be either NULL or a minimal starting dtype when - * the function is called. It is updated with the results of type - * promotion. This dtype does not get updated when processing NA objects. - * This is reset to NULL on failure. - * - * Returns 0 on success, -1 on failure. - */ - NPY_NO_EXPORT int -PyArray_DTypeFromObject(PyObject *obj, int maxdims, PyArray_Descr **out_dtype) -{ - int res; - - res = PyArray_DTypeFromObjectHelper(obj, maxdims, out_dtype, 0); - if (res == RETRY_WITH_STRING) { - res = PyArray_DTypeFromObjectHelper(obj, maxdims, - out_dtype, NPY_STRING); - if (res == RETRY_WITH_UNICODE) { - res = PyArray_DTypeFromObjectHelper(obj, maxdims, - out_dtype, NPY_UNICODE); - } - } - else if (res == RETRY_WITH_UNICODE) { - res = PyArray_DTypeFromObjectHelper(obj, maxdims, - out_dtype, NPY_UNICODE); - } - return res; -} /* * Get a suitable string dtype by calling `__str__`. * For `np.bytes_`, this assumes an ASCII encoding. */ -static PyArray_Descr * +NPY_NO_EXPORT PyArray_Descr * PyArray_DTypeFromObjectStringDiscovery( PyObject *obj, PyArray_Descr *last_dtype, int string_type) { @@ -159,8 +92,8 @@ PyArray_DTypeFromObjectStringDiscovery( return NULL; } if (last_dtype != NULL && - last_dtype->type_num == string_type && - last_dtype->elsize >= itemsize) { + last_dtype->type_num == string_type && + last_dtype->elsize >= itemsize) { Py_INCREF(last_dtype); return last_dtype; } @@ -172,348 +105,28 @@ PyArray_DTypeFromObjectStringDiscovery( return dtype; } + +/* + * This function is now identical to the new PyArray_DiscoverDTypeAndShape + * but only returns the the dtype. It should in most cases be slowly phased + * out. (Which may need some refactoring to PyArray_FromAny to make it simpler) + */ NPY_NO_EXPORT int -PyArray_DTypeFromObjectHelper(PyObject *obj, int maxdims, - PyArray_Descr **out_dtype, int string_type) +PyArray_DTypeFromObject(PyObject *obj, int maxdims, PyArray_Descr **out_dtype) { - int i, size; - PyArray_Descr *dtype = NULL; - PyObject *ip; - Py_buffer buffer_view; - /* types for sequence handling */ - PyObject ** objects; - PyObject * seq; - PyTypeObject * common_type; - - /* Check if it's an ndarray */ - if (PyArray_Check(obj)) { - dtype = PyArray_DESCR((PyArrayObject *)obj); - Py_INCREF(dtype); - goto promote_types; - } - - /* See if it's a python None */ - if (obj == Py_None) { - dtype = PyArray_DescrFromType(NPY_OBJECT); - if (dtype == NULL) { - goto fail; - } - goto promote_types; - } - /* Check if it's a NumPy scalar */ - else if (PyArray_IsScalar(obj, Generic)) { - if (!string_type) { - dtype = PyArray_DescrFromScalar(obj); - if (dtype == NULL) { - goto fail; - } - } - else { - dtype = PyArray_DTypeFromObjectStringDiscovery( - obj, *out_dtype, string_type); - if (dtype == NULL) { - goto fail; - } - - /* nothing to do, dtype is already correct */ - if (dtype == *out_dtype){ - Py_DECREF(dtype); - return 0; - } - } - goto promote_types; - } - - /* Check if it's a Python scalar */ - dtype = _array_find_python_scalar_type(obj); - if (dtype != NULL) { - if (string_type) { - /* dtype is not used in this (string discovery) branch */ - Py_DECREF(dtype); - dtype = PyArray_DTypeFromObjectStringDiscovery( - obj, *out_dtype, string_type); - if (dtype == NULL) { - goto fail; - } - - /* nothing to do, dtype is already correct */ - if (dtype == *out_dtype){ - Py_DECREF(dtype); - return 0; - } - } - goto promote_types; - } - - /* Check if it's an ASCII string */ - if (PyBytes_Check(obj)) { - int itemsize = PyString_GET_SIZE(obj); - - /* If it's already a big enough string, don't bother type promoting */ - if (*out_dtype != NULL && - (*out_dtype)->type_num == NPY_STRING && - (*out_dtype)->elsize >= itemsize) { - return 0; - } - dtype = PyArray_DescrNewFromType(NPY_STRING); - if (dtype == NULL) { - goto fail; - } - dtype->elsize = itemsize; - goto promote_types; - } - - /* Check if it's a Unicode string */ - if (PyUnicode_Check(obj)) { - int itemsize = PyUnicode_GetLength(obj); - if (itemsize < 0) { - goto fail; - } - itemsize *= 4; + coercion_cache_obj *cache = NULL; + npy_intp shape[NPY_MAXDIMS]; + int ndim; - /* - * If it's already a big enough unicode object, - * don't bother type promoting - */ - if (*out_dtype != NULL && - (*out_dtype)->type_num == NPY_UNICODE && - (*out_dtype)->elsize >= itemsize) { - return 0; - } - dtype = PyArray_DescrNewFromType(NPY_UNICODE); - if (dtype == NULL) { - goto fail; - } - dtype->elsize = itemsize; - goto promote_types; - } - - /* PEP 3118 buffer interface */ - if (PyObject_CheckBuffer(obj) == 1) { - memset(&buffer_view, 0, sizeof(Py_buffer)); - if (PyObject_GetBuffer(obj, &buffer_view, - PyBUF_FORMAT|PyBUF_STRIDES) == 0 || - PyObject_GetBuffer(obj, &buffer_view, - PyBUF_FORMAT|PyBUF_SIMPLE) == 0) { - - PyErr_Clear(); - dtype = _descriptor_from_pep3118_format(buffer_view.format); - PyBuffer_Release(&buffer_view); - if (dtype) { - goto promote_types; - } - } - else if (PyObject_GetBuffer(obj, &buffer_view, PyBUF_STRIDES) == 0 || - PyObject_GetBuffer(obj, &buffer_view, PyBUF_SIMPLE) == 0) { - - PyErr_Clear(); - dtype = PyArray_DescrNewFromType(NPY_VOID); - dtype->elsize = buffer_view.itemsize; - PyBuffer_Release(&buffer_view); - goto promote_types; - } - else { - PyErr_Clear(); - } - } - - /* The array interface */ - ip = PyArray_LookupSpecial_OnInstance(obj, "__array_interface__"); - if (ip != NULL) { - if (PyDict_Check(ip)) { - PyObject *typestr; - PyObject *tmp = NULL; - typestr = _PyDict_GetItemStringWithError(ip, "typestr"); - if (typestr == NULL && PyErr_Occurred()) { - goto fail; - } - /* Allow unicode type strings */ - if (typestr && PyUnicode_Check(typestr)) { - tmp = PyUnicode_AsASCIIString(typestr); - typestr = tmp; - } - if (typestr && PyBytes_Check(typestr)) { - dtype =_array_typedescr_fromstr(PyBytes_AS_STRING(typestr)); - if (tmp == typestr) { - Py_DECREF(tmp); - } - Py_DECREF(ip); - if (dtype == NULL) { - goto fail; - } - goto promote_types; - } - } - Py_DECREF(ip); - } - else if (PyErr_Occurred()) { - PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */ - } - - - /* The array struct interface */ - ip = PyArray_LookupSpecial_OnInstance(obj, "__array_struct__"); - if (ip != NULL) { - PyArrayInterface *inter; - char buf[40]; - - if (NpyCapsule_Check(ip)) { - inter = (PyArrayInterface *)NpyCapsule_AsVoidPtr(ip); - if (inter->two == 2) { - PyOS_snprintf(buf, sizeof(buf), - "|%c%d", inter->typekind, inter->itemsize); - dtype = _array_typedescr_fromstr(buf); - Py_DECREF(ip); - if (dtype == NULL) { - goto fail; - } - goto promote_types; - } - } - Py_DECREF(ip); - } - else if (PyErr_Occurred()) { - PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */ - } - - /* The __array__ attribute */ - ip = PyArray_LookupSpecial_OnInstance(obj, "__array__"); - if (ip != NULL) { - Py_DECREF(ip); - ip = PyObject_CallMethod(obj, "__array__", NULL); - if(ip && PyArray_Check(ip)) { - dtype = PyArray_DESCR((PyArrayObject *)ip); - Py_INCREF(dtype); - Py_DECREF(ip); - goto promote_types; - } - Py_XDECREF(ip); - if (PyErr_Occurred()) { - goto fail; - } - } - else if (PyErr_Occurred()) { - PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */ - } - - /* - * If we reached the maximum recursion depth without hitting one - * of the above cases, and obj isn't a sequence-like object, the output - * dtype should be either OBJECT or a user-defined type. - * - * Note that some libraries define sequence-like classes but want them to - * be treated as objects, and they expect numpy to treat it as an object if - * __len__ is not defined. - */ - if (maxdims == 0 || !PySequence_Check(obj) || PySequence_Size(obj) < 0) { - /* clear any PySequence_Size error which corrupts further calls */ - PyErr_Clear(); - - if (*out_dtype == NULL || (*out_dtype)->type_num != NPY_OBJECT) { - Py_XDECREF(*out_dtype); - *out_dtype = PyArray_DescrFromType(NPY_OBJECT); - if (*out_dtype == NULL) { - return -1; - } - } - return 0; - } - - /* - * The C-API recommends calling PySequence_Fast before any of the other - * PySequence_Fast* functions. This is required for PyPy - */ - seq = PySequence_Fast(obj, "Could not convert object to sequence"); - if (seq == NULL) { - goto fail; - } - - /* Recursive case, first check the sequence contains only one type */ - size = PySequence_Fast_GET_SIZE(seq); - /* objects is borrowed, do not release seq */ - objects = PySequence_Fast_ITEMS(seq); - common_type = size > 0 ? Py_TYPE(objects[0]) : NULL; - for (i = 1; i < size; ++i) { - if (Py_TYPE(objects[i]) != common_type) { - common_type = NULL; - break; - } - } - - /* all types are the same and scalar, one recursive call is enough */ - if (common_type != NULL && !string_type && - (common_type == &PyFloat_Type || -/* TODO: we could add longs if we add a range check */ - common_type == &PyBool_Type || - common_type == &PyComplex_Type)) { - size = 1; - } - - /* Recursive call for each sequence item */ - for (i = 0; i < size; ++i) { - int res = PyArray_DTypeFromObjectHelper(objects[i], maxdims - 1, - out_dtype, string_type); - if (res < 0) { - Py_DECREF(seq); - goto fail; - } - else if (res > 0) { - Py_DECREF(seq); - return res; - } + ndim = PyArray_DiscoverDTypeAndShape( + obj, maxdims, shape, &cache, NULL, NULL, out_dtype); + if (ndim < 0) { + return -1; } - - Py_DECREF(seq); - + npy_free_coercion_cache(cache); return 0; - - -promote_types: - /* Set 'out_dtype' if it's NULL */ - if (*out_dtype == NULL) { - if (!string_type && dtype->type_num == NPY_STRING) { - Py_DECREF(dtype); - return RETRY_WITH_STRING; - } - if (!string_type && dtype->type_num == NPY_UNICODE) { - Py_DECREF(dtype); - return RETRY_WITH_UNICODE; - } - *out_dtype = dtype; - return 0; - } - /* Do type promotion with 'out_dtype' */ - else { - PyArray_Descr *res_dtype = PyArray_PromoteTypes(dtype, *out_dtype); - Py_DECREF(dtype); - if (res_dtype == NULL) { - goto fail; - } - if (!string_type && - res_dtype->type_num == NPY_UNICODE && - (*out_dtype)->type_num != NPY_UNICODE) { - Py_DECREF(res_dtype); - return RETRY_WITH_UNICODE; - } - if (!string_type && - res_dtype->type_num == NPY_STRING && - (*out_dtype)->type_num != NPY_STRING) { - Py_DECREF(res_dtype); - return RETRY_WITH_STRING; - } - Py_DECREF(*out_dtype); - *out_dtype = res_dtype; - return 0; - } - -fail: - Py_XDECREF(*out_dtype); - *out_dtype = NULL; - return -1; } -#undef RETRY_WITH_STRING -#undef RETRY_WITH_UNICODE /* new reference */ NPY_NO_EXPORT PyArray_Descr * diff --git a/numpy/core/src/multiarray/common.h b/numpy/core/src/multiarray/common.h index 4ba25c079..793cefaf8 100644 --- a/numpy/core/src/multiarray/common.h +++ b/numpy/core/src/multiarray/common.h @@ -20,6 +20,11 @@ #define NPY_BEGIN_THREADS_NDITER(iter) #endif + +NPY_NO_EXPORT PyArray_Descr * +PyArray_DTypeFromObjectStringDiscovery( + PyObject *obj, PyArray_Descr *last_dtype, int string_type); + /* * Recursively examines the object to determine an appropriate dtype * to use for converting to an ndarray. diff --git a/numpy/core/src/multiarray/compiled_base.c b/numpy/core/src/multiarray/compiled_base.c index 7a232b5d9..a8e4aa789 100644 --- a/numpy/core/src/multiarray/compiled_base.c +++ b/numpy/core/src/multiarray/compiled_base.c @@ -1504,6 +1504,17 @@ arr_add_docstring(PyObject *NPY_UNUSED(dummy), PyObject *args) #include <emmintrin.h> #endif +#ifdef NPY_HAVE_NEON + typedef npy_uint64 uint64_unaligned __attribute__((aligned(16))); + static NPY_INLINE int32_t + sign_mask(uint8x16_t input) + { + int8x8_t m0 = vcreate_s8(0x0706050403020100ULL); + uint8x16_t v0 = vshlq_u8(vshrq_n_u8(input, 7), vcombine_s8(m0, m0)); + uint64x2_t v1 = vpaddlq_u32(vpaddlq_u16(vpaddlq_u8(v0))); + return (int)vgetq_lane_u64(v1, 0) + ((int)vgetq_lane_u64(v1, 1) << 8); + } +#endif /* * This function packs boolean values in the input array into the bits of a * byte array. Truth values are determined as usual: 0 is false, everything @@ -1543,6 +1554,7 @@ pack_inner(const char *inptr, a = npy_bswap8(a); b = npy_bswap8(b); } + /* note x86 can load unaligned */ __m128i v = _mm_set_epi64(_m_from_int64(b), _m_from_int64(a)); /* false -> 0x00 and true -> 0xFF (there is no cmpneq) */ @@ -1558,6 +1570,34 @@ pack_inner(const char *inptr, inptr += 16; } } +#elif defined NPY_HAVE_NEON + if (in_stride == 1 && element_size == 1 && n_out > 2) { + /* don't handle non-full 8-byte remainder */ + npy_intp vn_out = n_out - (remain ? 1 : 0); + vn_out -= (vn_out & 1); + for (index = 0; index < vn_out; index += 2) { + unsigned int r; + npy_uint64 a = *((uint64_unaligned*)inptr); + npy_uint64 b = *((uint64_unaligned*)(inptr + 8)); + if (order == 'b') { + a = npy_bswap8(a); + b = npy_bswap8(b); + } + uint64x2_t v = vcombine_u64(vcreate_u64(a), vcreate_u64(b)); + uint64x2_t zero = vdupq_n_u64(0); + /* false -> 0x00 and true -> 0xFF */ + v = vreinterpretq_u64_u8(vmvnq_u8(vceqq_u8(vreinterpretq_u8_u64(v), vreinterpretq_u8_u64(zero)))); + /* extract msb of 16 bytes and pack it into 16 bit */ + uint8x16_t input = vreinterpretq_u8_u64(v); + r = sign_mask(input); + /* store result */ + memcpy(outptr, &r, 1); + outptr += out_stride; + memcpy(outptr, (char*)&r + 1, 1); + outptr += out_stride; + inptr += 16; + } + } #endif if (remain == 0) { /* assumes n_in > 0 */ diff --git a/numpy/core/src/multiarray/conversion_utils.c b/numpy/core/src/multiarray/conversion_utils.c index 0e49b0d63..e41fdc8f1 100644 --- a/numpy/core/src/multiarray/conversion_utils.c +++ b/numpy/core/src/multiarray/conversion_utils.c @@ -371,8 +371,11 @@ string_converter_helper( int ret = str_func(str, length, out); Py_DECREF(str_object); if (ret < 0) { + /* str_func returns -1 without an exception if the value is wrong */ + if (!PyErr_Occurred()) { PyErr_Format(PyExc_ValueError, "%s %s (got %R)", name, message, object); + } return NPY_FAIL; } return NPY_SUCCEED; @@ -385,8 +388,8 @@ static int byteorder_parser(char const *str, Py_ssize_t length, void *data) if (length < 1) { return -1; } - else if (str[0] == NPY_BIG || str[0] == NPY_LITTLE - || str[0] == NPY_NATIVE || str[0] == NPY_IGNORE) { + else if (str[0] == NPY_BIG || str[0] == NPY_LITTLE || + str[0] == NPY_NATIVE || str[0] == NPY_IGNORE) { *endian = str[0]; return 0; } @@ -508,21 +511,36 @@ PyArray_SelectkindConverter(PyObject *obj, NPY_SELECTKIND *selectkind) static int searchside_parser(char const *str, Py_ssize_t length, void *data) { NPY_SEARCHSIDE *side = (NPY_SEARCHSIDE *)data; + int is_exact = 0; if (length < 1) { return -1; } else if (str[0] == 'l' || str[0] == 'L') { *side = NPY_SEARCHLEFT; - return 0; + is_exact = (length == 4 && strcmp(str, "left") == 0); } else if (str[0] == 'r' || str[0] == 'R') { *side = NPY_SEARCHRIGHT; - return 0; + is_exact = (length == 5 && strcmp(str, "right") == 0); } else { return -1; } + + /* Filters out the case sensitive/non-exact + * match inputs and other inputs and outputs DeprecationWarning + */ + if (!is_exact) { + /* NumPy 1.20, 2020-05-19 */ + if (DEPRECATE("inexact matches and case insensitive matches " + "for search side are deprecated, please use " + "one of 'left' or 'right' instead.") < 0) { + return -1; + } + } + + return 0; } /*NUMPY_API @@ -581,24 +599,40 @@ PyArray_OrderConverter(PyObject *object, NPY_ORDER *val) static int clipmode_parser(char const *str, Py_ssize_t length, void *data) { NPY_CLIPMODE *val = (NPY_CLIPMODE *)data; + int is_exact = 0; + if (length < 1) { return -1; } if (str[0] == 'C' || str[0] == 'c') { *val = NPY_CLIP; - return 0; + is_exact = (length == 4 && strcmp(str, "clip") == 0); } else if (str[0] == 'W' || str[0] == 'w') { *val = NPY_WRAP; - return 0; + is_exact = (length == 4 && strcmp(str, "wrap") == 0); } else if (str[0] == 'R' || str[0] == 'r') { *val = NPY_RAISE; - return 0; + is_exact = (length == 5 && strcmp(str, "raise") == 0); } else { return -1; } + + /* Filters out the case sensitive/non-exact + * match inputs and other inputs and outputs DeprecationWarning + */ + if (!is_exact) { + /* Numpy 1.20, 2020-05-19 */ + if (DEPRECATE("inexact matches and case insensitive matches " + "for clip mode are deprecated, please use " + "one of 'clip', 'raise', or 'wrap' instead.") < 0) { + return -1; + } + } + + return 0; } /*NUMPY_API diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c index 0390c92fc..94cd1e5fa 100644 --- a/numpy/core/src/multiarray/convert_datatype.c +++ b/numpy/core/src/multiarray/convert_datatype.c @@ -12,8 +12,10 @@ #include "npy_pycompat.h" #include "numpy/npy_math.h" +#include "array_coercion.h" #include "common.h" #include "ctors.h" +#include "dtypemeta.h" #include "scalartypes.h" #include "mapping.h" @@ -47,11 +49,11 @@ PyArray_CastToType(PyArrayObject *arr, PyArray_Descr *dtype, int is_f_order) { PyObject *out; - /* If the requested dtype is flexible, adapt it */ - dtype = PyArray_AdaptFlexibleDType((PyObject *)arr, PyArray_DESCR(arr), dtype); + Py_SETREF(dtype, PyArray_AdaptDescriptorToArray(arr, (PyObject *)dtype)); if (dtype == NULL) { return NULL; } + out = PyArray_NewFromDescr(Py_TYPE(arr), dtype, PyArray_NDIM(arr), PyArray_DIMS(arr), @@ -128,24 +130,22 @@ PyArray_GetCastFunc(PyArray_Descr *descr, int type_num) } /* + * Legacy function to find the correct dtype when casting from any built-in + * dtype to NPY_STRING, NPY_UNICODE, NPY_VOID, and NPY_DATETIME with generic + * units. + * * This function returns a dtype based on flex_dtype and the values in - * data_dtype and data_obj. It also calls Py_DECREF on the flex_dtype. If the + * data_dtype. It also calls Py_DECREF on the flex_dtype. If the * flex_dtype is not flexible, it returns it as-is. * * Usually, if data_obj is not an array, dtype should be the result * given by the PyArray_GetArrayParamsFromObject function. * - * The data_obj may be NULL if just a dtype is known for the source. - * * If *flex_dtype is NULL, returns immediately, without setting an * exception, leaving any previous error handling intact. - * - * The current flexible dtypes include NPY_STRING, NPY_UNICODE, NPY_VOID, - * and NPY_DATETIME with generic units. */ NPY_NO_EXPORT PyArray_Descr * -PyArray_AdaptFlexibleDType(PyObject *data_obj, PyArray_Descr *data_dtype, - PyArray_Descr *flex_dtype) +PyArray_AdaptFlexibleDType(PyArray_Descr *data_dtype, PyArray_Descr *flex_dtype) { PyArray_DatetimeMetaData *meta; PyArray_Descr *retval = NULL; @@ -227,73 +227,6 @@ PyArray_AdaptFlexibleDType(PyObject *data_obj, PyArray_Descr *data_dtype, break; case NPY_OBJECT: size = 64; - if ((flex_type_num == NPY_STRING || - flex_type_num == NPY_UNICODE) && - data_obj != NULL) { - PyObject *list; - - if (PyArray_CheckScalar(data_obj)) { - list = PyArray_ToList((PyArrayObject *)data_obj); - if (list != NULL) { - PyObject *s = PyObject_Str(list); - if (s == NULL) { - Py_DECREF(list); - Py_DECREF(retval); - return NULL; - } - else { - size = PyObject_Length(s); - Py_DECREF(s); - } - Py_DECREF(list); - } - } - else if (PyArray_Check(data_obj)) { - /* - * Convert data array to list of objects since - * GetArrayParamsFromObject won't iterate over - * array. - */ - PyArray_Descr *dtype = NULL; - PyArrayObject *arr = NULL; - int result; - int ndim = 0; - npy_intp dims[NPY_MAXDIMS]; - list = PyArray_ToList((PyArrayObject *)data_obj); - result = PyArray_GetArrayParamsFromObject_int( - list, - retval, - 0, &dtype, - &ndim, dims, &arr); - Py_DECREF(list); - Py_XDECREF(arr); - if (result < 0) { - Py_XDECREF(dtype); - Py_DECREF(retval); - return NULL; - } - if (result == 0 && dtype != NULL) { - if (flex_type_num == NPY_UNICODE) { - size = dtype->elsize / 4; - } - else { - size = dtype->elsize; - } - } - Py_XDECREF(dtype); - } - else if (PyArray_IsPythonScalar(data_obj)) { - PyObject *s = PyObject_Str(data_obj); - if (s == NULL) { - Py_DECREF(retval); - return NULL; - } - else { - size = PyObject_Length(s); - Py_DECREF(s); - } - } - } break; case NPY_STRING: case NPY_VOID: @@ -353,12 +286,6 @@ PyArray_AdaptFlexibleDType(PyObject *data_obj, PyArray_Descr *data_dtype, retval = create_datetime_dtype(flex_type_num, meta); Py_DECREF(flex_dtype); } - else if (data_obj != NULL) { - /* Detect the unit from the input's data */ - retval = find_object_datetime_type(data_obj, - flex_type_num); - Py_DECREF(flex_dtype); - } } } else { @@ -1292,7 +1219,7 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) PyArray_Descr *temp = PyArray_DescrNew(type1); PyDataType_MAKEUNSIZED(temp); - temp = PyArray_AdaptFlexibleDType(NULL, type2, temp); + temp = PyArray_AdaptFlexibleDType(type2, temp); if (temp == NULL) { return NULL; } @@ -1333,7 +1260,7 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) PyArray_Descr *ret = NULL; PyArray_Descr *temp = PyArray_DescrNew(type1); PyDataType_MAKEUNSIZED(temp); - temp = PyArray_AdaptFlexibleDType(NULL, type2, temp); + temp = PyArray_AdaptFlexibleDType(type2, temp); if (temp == NULL) { return NULL; } @@ -1384,7 +1311,7 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) PyArray_Descr *ret = NULL; PyArray_Descr *temp = PyArray_DescrNew(type2); PyDataType_MAKEUNSIZED(temp); - temp = PyArray_AdaptFlexibleDType(NULL, type1, temp); + temp = PyArray_AdaptFlexibleDType(type1, temp); if (temp == NULL) { return NULL; } @@ -1404,7 +1331,7 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) PyArray_Descr *ret = NULL; PyArray_Descr *temp = PyArray_DescrNew(type2); PyDataType_MAKEUNSIZED(temp); - temp = PyArray_AdaptFlexibleDType(NULL, type1, temp); + temp = PyArray_AdaptFlexibleDType(type1, temp); if (temp == NULL) { return NULL; } @@ -1419,8 +1346,7 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) } break; case NPY_TIMEDELTA: - if (PyTypeNum_ISINTEGER(type_num1) || - PyTypeNum_ISFLOAT(type_num1)) { + if (PyTypeNum_ISSIGNED(type_num1)) { return ensure_dtype_nbo(type2); } break; @@ -2155,7 +2081,6 @@ PyArray_ObjectType(PyObject *op, int minimum_type) return NPY_NOTYPE; } } - if (PyArray_DTypeFromObject(op, NPY_MAXDIMS, &dtype) < 0) { return NPY_NOTYPE; } @@ -2163,6 +2088,19 @@ PyArray_ObjectType(PyObject *op, int minimum_type) if (dtype == NULL) { ret = NPY_DEFAULT_TYPE; } + else if (!NPY_DTYPE(dtype)->legacy) { + /* + * TODO: If we keep all type number style API working, by defining + * type numbers always. We may be able to allow this again. + */ + PyErr_Format(PyExc_TypeError, + "This function currently only supports native NumPy dtypes " + "and old-style user dtypes, but the dtype was %S.\n" + "(The function may need to be updated to support arbitrary" + "user dtypes.)", + dtype); + ret = NPY_NOTYPE; + } else { ret = dtype->type_num; } diff --git a/numpy/core/src/multiarray/convert_datatype.h b/numpy/core/src/multiarray/convert_datatype.h index 4a7d85187..9b7f39db2 100644 --- a/numpy/core/src/multiarray/convert_datatype.h +++ b/numpy/core/src/multiarray/convert_datatype.h @@ -47,7 +47,6 @@ npy_set_invalid_cast_error( * and NPY_DATETIME with generic units. */ NPY_NO_EXPORT PyArray_Descr * -PyArray_AdaptFlexibleDType(PyObject *data_obj, PyArray_Descr *data_dtype, - PyArray_Descr *flex_dtype); +PyArray_AdaptFlexibleDType(PyArray_Descr *data_dtype, PyArray_Descr *flex_dtype); #endif diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index 3c3bcb387..cb448756b 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -30,6 +30,7 @@ #include <assert.h> #include "get_attr_string.h" +#include "array_coercion.h" /* * Reading from a file or a string. @@ -52,9 +53,6 @@ typedef int (*next_element)(void **, void *, PyArray_Descr *, void *); typedef int (*skip_separator)(void **, const char *, void *); -static PyObject * -_array_from_array_like(PyObject *op, - PyArray_Descr *requested_dtype, npy_bool writeable, PyObject *context); static npy_bool string_is_fully_read(char const* start, char const* end) { @@ -455,420 +453,169 @@ copy_and_swap(void *dst, void *src, int itemsize, npy_intp numitems, } } + /* - * adapted from Numarray, - * a: destination array - * s: source object, array or sequence - * dim: current recursion dimension, must be 0 on first call - * dst: must be NULL on first call - * it is a view on the destination array viewing the place where to put the - * data of the current recursion + * Recursive helper to assign using a coercion cache. This function + * must consume the cache depth first, just as the cache was originally + * produced. */ -static int -setArrayFromSequence(PyArrayObject *a, PyObject *s, - int dim, PyArrayObject * dst) +NPY_NO_EXPORT int +PyArray_AssignFromCache_Recursive( + PyArrayObject *self, const int ndim, coercion_cache_obj **cache) { - Py_ssize_t i, slen; - int res = -1; - - /* first recursion, view equal destination */ - if (dst == NULL) - dst = a; + /* Consume first cache element by extracting information and freeing it */ + PyObject *original_obj = (*cache)->converted_obj; + PyObject *obj = (*cache)->arr_or_sequence; + Py_INCREF(obj); + npy_bool sequence = (*cache)->sequence; + int depth = (*cache)->depth; + *cache = npy_unlink_coercion_cache(*cache); /* - * This code is to ensure that the sequence access below will - * return a lower-dimensional sequence. + * The maximum depth is special (specifically for objects), but usually + * unrolled in the sequence branch below. */ - - /* INCREF on entry DECREF on exit */ - Py_INCREF(s); - - PyObject *seq = NULL; - - if (PyArray_Check(s)) { - if (!(PyArray_CheckExact(s))) { + if (NPY_UNLIKELY(depth == ndim)) { + /* + * We have reached the maximum depth. We should simply assign to the + * element in principle. There is one exception. If this is a 0-D + * array being stored into a 0-D array (but we do not reach here then). + */ + if (PyArray_ISOBJECT(self)) { + assert(ndim != 0); /* guaranteed by PyArray_AssignFromCache */ + assert(PyArray_NDIM(self) == 0); + Py_DECREF(obj); + return PyArray_Pack(PyArray_DESCR(self), PyArray_BYTES(self), + original_obj); + } + if (sequence) { /* - * make sure a base-class array is used so that the dimensionality - * reduction assumption is correct. + * Sanity check which may be removed, the error is raised already + * in `PyArray_DiscoverDTypeAndShape`. */ - /* This will DECREF(s) if replaced */ - s = PyArray_EnsureArray(s); - if (s == NULL) { - goto fail; - } - } - - /* dst points to correct array subsection */ - if (PyArray_CopyInto(dst, (PyArrayObject *)s) < 0) { + assert(0); + PyErr_SetString(PyExc_RuntimeError, + "setting an array element with a sequence"); goto fail; } - - Py_DECREF(s); - return 0; - } - - if (dim > PyArray_NDIM(a)) { - PyErr_Format(PyExc_ValueError, - "setArrayFromSequence: sequence/array dimensions mismatch."); - goto fail; + else if (original_obj != obj || !PyArray_CheckExact(obj)) { + /* + * If the leave node is an array-like, but not a numpy array, + * we pretend it is an arbitrary scalar. This means that in + * most cases (where the dtype is int or float), we will end + * up using float(array-like), or int(array-like). That does + * not support general casting, but helps Quantity and masked + * arrays, because it allows them to raise an error when + * `__float__()` or `__int__()` is called. + */ + Py_DECREF(obj); + return PyArray_SETITEM(self, PyArray_BYTES(self), original_obj); + } } - /* Try __array__ before using s as a sequence */ - PyObject *tmp = _array_from_array_like(s, NULL, 0, NULL); - if (tmp == NULL) { - goto fail; - } - else if (tmp == Py_NotImplemented) { - Py_DECREF(tmp); + /* The element is either a sequence, or an array */ + if (!sequence) { + /* Straight forward array assignment */ + assert(PyArray_Check(obj)); + if (PyArray_CopyInto(self, (PyArrayObject *)obj) < 0) { + goto fail; + } } else { - int r = PyArray_CopyInto(dst, (PyArrayObject *)tmp); - Py_DECREF(tmp); - if (r < 0) { + assert(depth != ndim); + npy_intp length = PySequence_Length(obj); + if (length != PyArray_DIMS(self)[0]) { + PyErr_SetString(PyExc_RuntimeError, + "Inconsistent object during array creation? " + "Content of sequences changed (length inconsistent)."); goto fail; } - Py_DECREF(s); - return 0; - } - - seq = PySequence_Fast(s, "Could not convert object to sequence"); - if (seq == NULL) { - goto fail; - } - slen = PySequence_Fast_GET_SIZE(seq); - /* - * Either the dimensions match, or the sequence has length 1 and can - * be broadcast to the destination. - */ - if (slen != PyArray_DIMS(a)[dim] && slen != 1) { - PyErr_Format(PyExc_ValueError, - "cannot copy sequence with size %zd to array axis " - "with dimension %" NPY_INTP_FMT, slen, PyArray_DIMS(a)[dim]); - goto fail; - } + for (npy_intp i = 0; i < length; i++) { + PyObject *value = PySequence_Fast_GET_ITEM(obj, i); - /* Broadcast the one element from the sequence to all the outputs */ - if (slen == 1) { - PyObject *o = PySequence_Fast_GET_ITEM(seq, 0); - npy_intp alen = PyArray_DIM(a, dim); - - for (i = 0; i < alen; i++) { - if ((PyArray_NDIM(a) - dim) > 1) { - PyArrayObject * tmp = - (PyArrayObject *)array_item_asarray(dst, i); - if (tmp == NULL) { + if (*cache == NULL || (*cache)->converted_obj != value || + (*cache)->depth != depth + 1) { + if (ndim != depth + 1) { + PyErr_SetString(PyExc_RuntimeError, + "Inconsistent object during array creation? " + "Content of sequences changed (now too shallow)."); goto fail; } - - res = setArrayFromSequence(a, o, dim+1, tmp); - Py_DECREF(tmp); - } - else { - char * b = (PyArray_BYTES(dst) + i * PyArray_STRIDES(dst)[0]); - res = PyArray_SETITEM(dst, b, o); - } - if (res < 0) { - goto fail; - } - } - } - /* Copy element by element */ - else { - for (i = 0; i < slen; i++) { - PyObject * o = PySequence_Fast_GET_ITEM(seq, i); - if ((PyArray_NDIM(a) - dim) > 1) { - PyArrayObject * tmp = - (PyArrayObject *)array_item_asarray(dst, i); - if (tmp == NULL) { + /* Straight forward assignment of elements */ + char *item; + item = (PyArray_BYTES(self) + i * PyArray_STRIDES(self)[0]); + if (PyArray_Pack(PyArray_DESCR(self), item, value) < 0) { goto fail; } - - res = setArrayFromSequence(a, o, dim+1, tmp); - Py_DECREF(tmp); - } - else { - char * b = (PyArray_BYTES(dst) + i * PyArray_STRIDES(dst)[0]); - res = PyArray_SETITEM(dst, b, o); - } - if (res < 0) { - goto fail; - } - } - } - - Py_DECREF(seq); - Py_DECREF(s); - return 0; - - fail: - Py_XDECREF(seq); - Py_DECREF(s); - return res; -} - -NPY_NO_EXPORT int -PyArray_AssignFromSequence(PyArrayObject *self, PyObject *v) -{ - if (!PySequence_Check(v)) { - PyErr_SetString(PyExc_ValueError, - "assignment from non-sequence"); - return -1; - } - if (PyArray_NDIM(self) == 0) { - PyErr_SetString(PyExc_ValueError, - "assignment to 0-d array"); - return -1; - } - return setArrayFromSequence(self, v, 0, NULL); -} - -/* - * The rest of this code is to build the right kind of array - * from a python object. - */ - -static int -discover_itemsize(PyObject *s, int nd, int *itemsize, int string_type) -{ - int r; - npy_intp n, i; - - if (PyArray_Check(s)) { - *itemsize = PyArray_MAX(*itemsize, PyArray_ITEMSIZE((PyArrayObject *)s)); - return 0; - } - - if ((nd == 0) || PyString_Check(s) || - PyMemoryView_Check(s) || PyUnicode_Check(s)) { - /* If an object has no length, leave it be */ - if (string_type && s != NULL && - !PyString_Check(s) && !PyUnicode_Check(s)) { - PyObject *s_string = NULL; - if (string_type == NPY_STRING) { - s_string = PyObject_Str(s); } else { - s_string = PyObject_Str(s); - } - if (s_string) { - n = PyObject_Length(s_string); - Py_DECREF(s_string); - } - else { - n = -1; + PyArrayObject *view; + view = (PyArrayObject *)array_item_asarray(self, i); + if (view < 0) { + goto fail; + } + if (PyArray_AssignFromCache_Recursive(view, ndim, cache) < 0) { + Py_DECREF(view); + goto fail; + } + Py_DECREF(view); } } - else { - n = PyObject_Length(s); - } - if (n == -1) { - PyErr_Clear(); - } - else { - *itemsize = PyArray_MAX(*itemsize, n); - } - return 0; } - - n = PySequence_Length(s); - for (i = 0; i < n; i++) { - PyObject *e = PySequence_GetItem(s,i); - - if (e == NULL) { - return -1; - } - - r = discover_itemsize(e, nd - 1, itemsize, string_type); - Py_DECREF(e); - if (r == -1) { - return -1; - } - } - + Py_DECREF(obj); return 0; -} - - -typedef enum { - DISCOVERED_OK = 0, - DISCOVERED_RAGGED = 1, - DISCOVERED_OBJECT = 2 -} discovered_t; - -static void -_discover_dimensions_array(PyArrayObject *arr, int *maxndim, npy_intp *d) { - if (PyArray_NDIM(arr) < *maxndim) { - *maxndim = PyArray_NDIM(arr); - } - for (int i = 0; i < *maxndim; i++) { - d[i] = PyArray_DIM(arr, i); - } + fail: + Py_DECREF(obj); + return -1; } -/* - * Take an arbitrary object and discover how many dimensions it - * has, filling in the dimensions as we go. +/** + * Fills an item based on a coercion cache object. It consumes the cache + * object while doing so. + * + * @param self Array to fill. + * @param cache coercion_cache_object, will be consumed. The cache must not + * contain a single array (must start with a sequence). The array case + * should be handled by `PyArray_FromArray()` before. + * @return 0 on success -1 on failure. */ -static int -discover_dimensions(PyObject *obj, int *maxndim, npy_intp *d, int check_it, - int stop_at_string, int stop_at_tuple, - discovered_t *out_is_object) -{ - PyObject *e; - npy_intp n, i; - PyObject * seq; - - if (*maxndim == 0) { - return 0; - } - - /* obj is an Array */ - if (PyArray_Check(obj)) { - _discover_dimensions_array((PyArrayObject *)obj, maxndim, d); - return 0; - } - - /* obj is a Scalar */ - if (PyArray_IsScalar(obj, Generic)) { - *maxndim = 0; - return 0; - } - - /* obj is not a Sequence */ - if (!PySequence_Check(obj) || - PySequence_Length(obj) < 0) { - *maxndim = 0; - PyErr_Clear(); - return 0; - } - - /* obj is a String */ - if (PyString_Check(obj) || - PyUnicode_Check(obj)) { - if (stop_at_string) { - *maxndim = 0; - } - else { - d[0] = PySequence_Length(obj); - *maxndim = 1; - } - return 0; - } - - /* obj is a Tuple, but tuples aren't expanded */ - if (stop_at_tuple && PyTuple_Check(obj)) { - *maxndim = 0; - return 0; - } - +NPY_NO_EXPORT int +PyArray_AssignFromCache(PyArrayObject *self, coercion_cache_obj *cache) { + int ndim = PyArray_NDIM(self); /* - * In the future, the result of `_array_from_array_like` should possibly - * be cached. This may require passing the correct dtype/writable - * information already in the dimension discovery step (if they are - * distinct steps). + * Do not support ndim == 0 now with an array in the cache. + * The ndim == 0 is special because np.array(np.array(0), dtype=object) + * should unpack the inner array. + * Since the single-array case is special, it is handled previously + * in either case. */ - e = _array_from_array_like(obj, NULL, NPY_FALSE, NULL); - if (e == Py_NotImplemented) { - Py_DECREF(e); - } - else if (e != NULL) { - _discover_dimensions_array((PyArrayObject *)e, maxndim, d); - Py_DECREF(e); - return 0; - } - else if (PyErr_Occurred()) { - /* TODO[gh-14801]: propagate crashes during attribute access? */ - PyErr_Clear(); - } - - seq = PySequence_Fast(obj, "Could not convert object to sequence"); - if (seq == NULL) { - /* - * PySequence_Check detects whether an old type object is a - * sequence by the presence of the __getitem__ attribute, and - * for new type objects that aren't dictionaries by the - * presence of the __len__ attribute as well. In either case it - * is possible to have an object that tests as a sequence but - * doesn't behave as a sequence and consequently, the - * PySequence_GetItem call can fail. When that happens and the - * object looks like a dictionary, we truncate the dimensions - * and set the object creation flag, otherwise we pass the - * error back up the call chain. - */ - if (PyErr_ExceptionMatches(PyExc_KeyError)) { - PyErr_Clear(); - *maxndim = 0; - *out_is_object = DISCOVERED_OBJECT; - return 0; - } - else { - return -1; - } - } - n = PySequence_Fast_GET_SIZE(seq); + assert(cache->sequence); + assert(ndim != 0); /* guaranteed if cache contains a sequence */ - d[0] = n; - - /* 1-dimensional sequence */ - if (n == 0 || *maxndim == 1) { - *maxndim = 1; - Py_DECREF(seq); - return 0; + if (PyArray_AssignFromCache_Recursive(self, ndim, &cache) < 0) { + /* free the remaining cache. */ + npy_free_coercion_cache(cache); + return -1; } - else { - int all_elems_maxndim = *maxndim - 1; - npy_intp *all_elems_d = d + 1; - int all_dimensions_match = 1; - - /* Get the dimensions of the first item as a baseline */ - PyObject *first = PySequence_Fast_GET_ITEM(seq, 0); - if (discover_dimensions( - first, &all_elems_maxndim, all_elems_d, check_it, - stop_at_string, stop_at_tuple, out_is_object) < 0) { - Py_DECREF(seq); - return -1; - } - - /* Compare the dimensions of all the remaining items */ - for (i = 1; i < n; ++i) { - int j; - int elem_maxndim = *maxndim - 1; - npy_intp elem_d[NPY_MAXDIMS]; - - PyObject *elem = PySequence_Fast_GET_ITEM(seq, i); - if (discover_dimensions( - elem, &elem_maxndim, elem_d, check_it, - stop_at_string, stop_at_tuple, out_is_object) < 0) { - Py_DECREF(seq); - return -1; - } - /* Find the number of left-dimensions which match, j */ - for (j = 0; j < elem_maxndim && j < all_elems_maxndim; ++j) { - if (elem_d[j] != all_elems_d[j]) { - break; - } - } - if (j != elem_maxndim || j != all_elems_maxndim) { - all_dimensions_match = 0; - } - all_elems_maxndim = j; - } - *maxndim = all_elems_maxndim + 1; - if (!all_dimensions_match) { - /* typically results in an array containing variable-length lists */ - *out_is_object = DISCOVERED_RAGGED; - } + /* + * Sanity check, this is the initial call, and when it returns, the + * cache has to be fully consumed, otherwise something is wrong. + * NOTE: May be nicer to put into a recursion helper. + */ + if (cache != NULL) { + PyErr_SetString(PyExc_RuntimeError, + "Inconsistent object during array creation? " + "Content of sequences changed (cache not consumed)."); + return -1; } - - Py_DECREF(seq); - return 0; } + static void raise_memory_error(int nd, npy_intp const *dims, PyArray_Descr *descr) { @@ -1518,7 +1265,7 @@ fail: * or NULL with an error set. (A new reference to Py_NotImplemented * is returned.) */ -static PyObject * +NPY_NO_EXPORT PyObject * _array_from_array_like(PyObject *op, PyArray_Descr *requested_dtype, npy_bool writeable, PyObject *context) { PyObject* tmp; @@ -1598,313 +1345,25 @@ _array_from_array_like(PyObject *op, } -/* - * Retrieves the array parameters for viewing/converting an arbitrary - * PyObject* to a NumPy array. This allows the "innate type and shape" - * of Python list-of-lists to be discovered without - * actually converting to an array. - * - * In some cases, such as structured arrays and the __array__ interface, - * a data type needs to be used to make sense of the object. When - * this is needed, provide a Descr for 'requested_dtype', otherwise - * provide NULL. This reference is not stolen. Also, if the requested - * dtype doesn't modify the interpretation of the input, out_dtype will - * still get the "innate" dtype of the object, not the dtype passed - * in 'requested_dtype'. - * - * If writing to the value in 'op' is desired, set the boolean - * 'writeable' to 1. This raises an error when 'op' is a scalar, list - * of lists, or other non-writeable 'op'. - * - * Result: When success (0 return value) is returned, either out_arr - * is filled with a non-NULL PyArrayObject and - * the rest of the parameters are untouched, or out_arr is - * filled with NULL, and the rest of the parameters are - * filled. - * - * Typical usage: - * - * PyArrayObject *arr = NULL; - * PyArray_Descr *dtype = NULL; - * int ndim = 0; - * npy_intp dims[NPY_MAXDIMS]; - * - * if (PyArray_GetArrayParamsFromObject(op, NULL, 1, &dtype, - * &ndim, dims, &arr, NULL) < 0) { - * return NULL; - * } - * if (arr == NULL) { - * ... validate/change dtype, validate flags, ndim, etc ... - * // Could make custom strides here too - * arr = PyArray_NewFromDescr(&PyArray_Type, dtype, ndim, - * dims, NULL, - * is_f_order ? NPY_ARRAY_F_CONTIGUOUS : 0, - * NULL); - * if (arr == NULL) { - * return NULL; - * } - * if (PyArray_CopyObject(arr, op) < 0) { - * Py_DECREF(arr); - * return NULL; - * } - * } - * else { - * ... in this case the other parameters weren't filled, just - * validate and possibly copy arr itself ... - * } - * ... use arr ... - */ +/*NUMPY_API*/ NPY_NO_EXPORT int -PyArray_GetArrayParamsFromObject_int(PyObject *op, - PyArray_Descr *requested_dtype, - npy_bool writeable, - PyArray_Descr **out_dtype, - int *out_ndim, npy_intp *out_dims, - PyArrayObject **out_arr) +PyArray_GetArrayParamsFromObject(PyObject *NPY_UNUSED(op), + PyArray_Descr *NPY_UNUSED(requested_dtype), + npy_bool NPY_UNUSED(writeable), + PyArray_Descr **NPY_UNUSED(out_dtype), + int *NPY_UNUSED(out_ndim), npy_intp *NPY_UNUSED(out_dims), + PyArrayObject **NPY_UNUSED(out_arr), PyObject *NPY_UNUSED(context)) { - PyObject *tmp; - - /* If op is an array */ - if (PyArray_Check(op)) { - if (writeable - && PyArray_FailUnlessWriteable((PyArrayObject *)op, "array") < 0) { - return -1; - } - Py_INCREF(op); - *out_arr = (PyArrayObject *)op; - return 0; - } - - /* If op is a NumPy scalar */ - if (PyArray_IsScalar(op, Generic)) { - if (writeable) { - PyErr_SetString(PyExc_RuntimeError, - "cannot write to scalar"); - return -1; - } - *out_dtype = PyArray_DescrFromScalar(op); - if (*out_dtype == NULL) { - return -1; - } - *out_ndim = 0; - *out_arr = NULL; - return 0; - } - - /* If op is a Python scalar */ - *out_dtype = _array_find_python_scalar_type(op); - if (*out_dtype != NULL) { - if (writeable) { - PyErr_SetString(PyExc_RuntimeError, - "cannot write to scalar"); - Py_DECREF(*out_dtype); - return -1; - } - *out_ndim = 0; - *out_arr = NULL; - return 0; - } - - /* If op is an array-like */ - tmp = _array_from_array_like(op, requested_dtype, writeable, NULL); - if (tmp == NULL) { - return -1; - } - else if (tmp != Py_NotImplemented) { - *out_arr = (PyArrayObject*) tmp; - return 0; - } - else { - Py_DECREF(Py_NotImplemented); - } - - /* Try to treat op as a list of lists */ - if (!writeable && PySequence_Check(op)) { - int check_it, stop_at_string, stop_at_tuple; - int type_num, type; - - /* - * Determine the type, using the requested data type if - * it will affect how the array is retrieved - */ - if (requested_dtype != NULL && ( - requested_dtype->type_num == NPY_STRING || - requested_dtype->type_num == NPY_UNICODE || - (requested_dtype->type_num == NPY_VOID && - (requested_dtype->names || requested_dtype->subarray)) || - requested_dtype->type == NPY_CHARLTR || - requested_dtype->type_num == NPY_OBJECT)) { - Py_INCREF(requested_dtype); - *out_dtype = requested_dtype; - } - else { - *out_dtype = NULL; - if (PyArray_DTypeFromObject(op, NPY_MAXDIMS, out_dtype) < 0) { - if (PyErr_ExceptionMatches(PyExc_MemoryError)) { - return -1; - } - /* Return NPY_OBJECT for most exceptions */ - else { - PyErr_Clear(); - *out_dtype = PyArray_DescrFromType(NPY_OBJECT); - if (*out_dtype == NULL) { - return -1; - } - } - } - if (*out_dtype == NULL) { - *out_dtype = PyArray_DescrFromType(NPY_DEFAULT_TYPE); - if (*out_dtype == NULL) { - return -1; - } - } - } - - type_num = (*out_dtype)->type_num; - type = (*out_dtype)->type; - - check_it = (type != NPY_CHARLTR); - stop_at_string = (type_num != NPY_STRING) || - (type == NPY_STRINGLTR); - stop_at_tuple = (type_num == NPY_VOID && - ((*out_dtype)->names || (*out_dtype)->subarray)); - - *out_ndim = NPY_MAXDIMS; - discovered_t is_object = DISCOVERED_OK; - if (discover_dimensions( - op, out_ndim, out_dims, check_it, - stop_at_string, stop_at_tuple, &is_object) < 0) { - Py_DECREF(*out_dtype); - if (PyErr_Occurred()) { - return -1; - } - *out_dtype = PyArray_DescrFromType(NPY_OBJECT); - if (*out_dtype == NULL) { - return -1; - } - *out_ndim = 0; - *out_arr = NULL; - return 0; - } - /* If object arrays are forced */ - if (is_object != DISCOVERED_OK) { - static PyObject *visibleDeprecationWarning = NULL; - npy_cache_import( - "numpy", "VisibleDeprecationWarning", - &visibleDeprecationWarning); - if (visibleDeprecationWarning == NULL) { - return -1; - } - if (is_object == DISCOVERED_RAGGED && requested_dtype == NULL) { - /* NumPy 1.19, 2019-11-01 */ - if (PyErr_WarnEx(visibleDeprecationWarning, "Creating an " - "ndarray from ragged nested sequences (which is a " - "list-or-tuple of lists-or-tuples-or ndarrays with " - "different lengths or shapes) is deprecated. If you " - "meant to do this, you must specify 'dtype=object' " - "when creating the ndarray", 1) < 0) - { - return -1; - } - } - /* either DISCOVERED_OBJECT or there is a requested_dtype */ - Py_DECREF(*out_dtype); - *out_dtype = PyArray_DescrFromType(NPY_OBJECT); - if (*out_dtype == NULL) { - return -1; - } - } - - if ((*out_dtype)->type == NPY_CHARLTR && (*out_ndim) > 0 && - out_dims[(*out_ndim) - 1] == 1) { - (*out_ndim) -= 1; - } - - /* If the type is flexible, determine its size */ - if (PyDataType_ISUNSIZED(*out_dtype) && - PyTypeNum_ISEXTENDED((*out_dtype)->type_num)) { - int itemsize = 0; - int string_type = 0; - if ((*out_dtype)->type_num == NPY_STRING || - (*out_dtype)->type_num == NPY_UNICODE) { - string_type = (*out_dtype)->type_num; - } - if (discover_itemsize(op, *out_ndim, &itemsize, string_type) < 0) { - Py_DECREF(*out_dtype); - if (PyErr_Occurred() && - PyErr_GivenExceptionMatches(PyErr_Occurred(), - PyExc_MemoryError)) { - return -1; - } - /* Say it's an OBJECT scalar if there's an error */ - PyErr_Clear(); - *out_dtype = PyArray_DescrFromType(NPY_OBJECT); - *out_ndim = 0; - *out_arr = NULL; - return 0; - } - if ((*out_dtype)->type_num == NPY_UNICODE) { - itemsize *= 4; - } - - if (itemsize != (*out_dtype)->elsize) { - PyArray_DESCR_REPLACE(*out_dtype); - (*out_dtype)->elsize = itemsize; - } - } - - *out_arr = NULL; - return 0; - } - - /* Anything can be viewed as an object, unless it needs to be writeable */ - if (!writeable) { - *out_dtype = PyArray_DescrFromType(NPY_OBJECT); - if (*out_dtype == NULL) { - return -1; - } - *out_ndim = 0; - *out_arr = NULL; - return 0; - } - + /* Deprecated in NumPy 1.19, removed in NumPy 1.20. */ PyErr_SetString(PyExc_RuntimeError, - "object cannot be viewed as a writeable numpy array"); + "PyArray_GetArrayParamsFromObject() C-API function is removed " + "`PyArray_FromAny()` should be used at this time. New C-API " + "may be exposed in the future (please do request this if it " + "would help you)."); return -1; } -/*NUMPY_API*/ -NPY_NO_EXPORT int -PyArray_GetArrayParamsFromObject(PyObject *op, - PyArray_Descr *requested_dtype, - npy_bool writeable, - PyArray_Descr **out_dtype, - int *out_ndim, npy_intp *out_dims, - PyArrayObject **out_arr, PyObject *context) -{ - /* NumPy 1.19, 2020-01-24 */ - if (DEPRECATE( - "PyArray_GetArrayParamsFromObject() C-API function is deprecated " - "and expected to be removed rapidly. If you are using it (i.e. see " - "this warning/error), please notify the NumPy developers. " - "As of now it is expected that any use case is served similarly " - "well by `PyArray_FromAny()` and this function is unused outside " - "of NumPy itself.") < 0) { - return -1; - } - - if (context != NULL) { - PyErr_SetString(PyExc_RuntimeError, "'context' must be NULL"); - return -1; - } - - return PyArray_GetArrayParamsFromObject_int(op, - requested_dtype, writeable, out_dtype, out_ndim, out_dims, - out_arr); -} - - /*NUMPY_API * Does not check for NPY_ARRAY_ENSURECOPY and NPY_ARRAY_NOTSWAPPED in flags * Steals a reference to newtype --- which can be NULL @@ -1919,6 +1378,7 @@ PyArray_FromAny(PyObject *op, PyArray_Descr *newtype, int min_depth, */ PyArrayObject *arr = NULL, *ret; PyArray_Descr *dtype = NULL; + coercion_cache_obj *cache = NULL; int ndim = 0; npy_intp dims[NPY_MAXDIMS]; @@ -1927,124 +1387,108 @@ PyArray_FromAny(PyObject *op, PyArray_Descr *newtype, int min_depth, return NULL; } - /* Get either the array or its parameters if it isn't an array */ - if (PyArray_GetArrayParamsFromObject_int(op, - newtype, 0, &dtype, &ndim, dims, &arr) < 0) { + PyArray_Descr *fixed_descriptor; + PyArray_DTypeMeta *fixed_DType; + if (PyArray_ExtractDTypeAndDescriptor((PyObject *)newtype, + &fixed_descriptor, &fixed_DType) < 0) { Py_XDECREF(newtype); return NULL; } + Py_XDECREF(newtype); - /* If the requested dtype is flexible, adapt it */ - if (newtype != NULL) { - newtype = PyArray_AdaptFlexibleDType((arr == NULL) ? op : (PyObject *)arr, - (dtype == NULL) ? PyArray_DESCR(arr) : dtype, - newtype); - if (newtype == NULL) { - return NULL; - } + ndim = PyArray_DiscoverDTypeAndShape(op, + NPY_MAXDIMS, dims, &cache, fixed_DType, fixed_descriptor, &dtype); + + Py_XDECREF(fixed_descriptor); + Py_XDECREF(fixed_DType); + if (ndim < 0) { + return NULL; + } + if (dtype == NULL) { + dtype = PyArray_DescrFromType(NPY_DEFAULT_TYPE); } - /* If we got dimensions and dtype instead of an array */ - if (arr == NULL) { - if ((flags & NPY_ARRAY_WRITEBACKIFCOPY) || - (flags & NPY_ARRAY_UPDATEIFCOPY)) { - Py_DECREF(dtype); - Py_XDECREF(newtype); - PyErr_SetString(PyExc_TypeError, - "WRITEBACKIFCOPY used for non-array input."); - return NULL; - } - else if (min_depth != 0 && ndim < min_depth) { - Py_DECREF(dtype); - Py_XDECREF(newtype); - PyErr_SetString(PyExc_ValueError, - "object of too small depth for desired array"); - ret = NULL; - } - else if (max_depth != 0 && ndim > max_depth) { - Py_DECREF(dtype); - Py_XDECREF(newtype); - PyErr_SetString(PyExc_ValueError, - "object too deep for desired array"); - ret = NULL; - } - else if (ndim == 0 && PyArray_IsScalar(op, Generic)) { - ret = (PyArrayObject *)PyArray_FromScalar(op, newtype); - Py_DECREF(dtype); - } - else { - if (newtype == NULL) { - newtype = dtype; - } - else { - /* - * TODO: would be nice to do this too, but it's - * a behavior change. It's also a bit tricky - * for downcasting to small integer and float - * types, and might be better to modify - * PyArray_AssignFromSequence and descr->f->setitem - * to have a 'casting' parameter and - * to check each value with scalar rules like - * in PyArray_MinScalarType. - */ - /* - if (!(flags&NPY_ARRAY_FORCECAST) && ndim > 0 && - !PyArray_CanCastTo(dtype, newtype)) { - Py_DECREF(dtype); - Py_XDECREF(newtype); - PyErr_SetString(PyExc_TypeError, - "object cannot be safely cast to array " - "of required type"); - return NULL; - } - */ - Py_DECREF(dtype); - } + if (min_depth != 0 && ndim < min_depth) { + PyErr_SetString(PyExc_ValueError, + "object of too small depth for desired array"); + Py_DECREF(dtype); + npy_free_coercion_cache(cache); + return NULL; + } + if (max_depth != 0 && ndim > max_depth) { + PyErr_SetString(PyExc_ValueError, + "object too deep for desired array"); + Py_DECREF(dtype); + npy_free_coercion_cache(cache); + return NULL; + } - /* Create an array and copy the data */ - ret = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, newtype, - ndim, dims, - NULL, NULL, - flags&NPY_ARRAY_F_CONTIGUOUS, NULL); - if (ret == NULL) { - return NULL; - } + /* Got the correct parameters, but the cache may already hold the result */ + if (cache != NULL && !(cache->sequence)) { + /* + * There is only a single array-like and it was converted, it + * may still have the incorrect type, but that is handled below. + */ + assert(cache->converted_obj == op); + arr = (PyArrayObject *)(cache->arr_or_sequence); + /* we may need to cast or assert flags (e.g. copy) */ + PyObject *res = PyArray_FromArray(arr, dtype, flags); + npy_unlink_coercion_cache(cache); + return res; + } + else if (cache == NULL && PyArray_IsScalar(op, Void) && + !(((PyVoidScalarObject *)op)->flags & NPY_ARRAY_OWNDATA) && + newtype == NULL) { + /* + * Special case, we return a *view* into void scalars, mainly to + * allow things similar to the "reversed" assignment: + * arr[indx]["field"] = val # instead of arr["field"][indx] = val + * + * It is unclear that this is necessary in this particular code path. + * Note that this path is only activated when the user did _not_ + * provide a dtype (newtype is NULL). + */ + assert(ndim == 0); - if (ndim > 0) { - if (PyArray_AssignFromSequence(ret, op) < 0) { - Py_DECREF(ret); - ret = NULL; - } - } - else { - if (PyArray_SETITEM(ret, PyArray_DATA(ret), op) < 0) { - Py_DECREF(ret); - ret = NULL; - } - } - } + return PyArray_NewFromDescrAndBase( + &PyArray_Type, dtype, + 0, NULL, NULL, + ((PyVoidScalarObject *)op)->obval, + ((PyVoidScalarObject *)op)->flags, + NULL, op); } - else { - if (min_depth != 0 && PyArray_NDIM(arr) < min_depth) { - PyErr_SetString(PyExc_ValueError, - "object of too small depth for desired array"); - Py_DECREF(arr); - Py_XDECREF(newtype); - ret = NULL; - } - else if (max_depth != 0 && PyArray_NDIM(arr) > max_depth) { - PyErr_SetString(PyExc_ValueError, - "object too deep for desired array"); - Py_DECREF(arr); - Py_XDECREF(newtype); - ret = NULL; - } - else { - ret = (PyArrayObject *)PyArray_FromArray(arr, newtype, flags); - Py_DECREF(arr); - } + + /* There was no array (or array-like) passed in directly. */ + if ((flags & NPY_ARRAY_WRITEBACKIFCOPY) || + (flags & NPY_ARRAY_UPDATEIFCOPY)) { + PyErr_SetString(PyExc_TypeError, + "WRITEBACKIFCOPY used for non-array input."); + Py_DECREF(dtype); + return NULL; } + /* Create a new array and copy the data */ + ret = (PyArrayObject *)PyArray_NewFromDescr( + &PyArray_Type, dtype, ndim, dims, NULL, NULL, + flags&NPY_ARRAY_F_CONTIGUOUS, NULL); + if (ret == NULL) { + return NULL; + } + if (cache == NULL) { + /* This is a single item. Set it directly. */ + assert(ndim == 0); + if (PyArray_Pack(PyArray_DESCR(ret), PyArray_DATA(ret), op) < 0) { + Py_DECREF(ret); + return NULL; + } + return (PyObject *)ret; + } + assert(ndim != 0); + assert(op == cache->converted_obj); + if (PyArray_AssignFromCache(ret, cache) < 0) { + Py_DECREF(ret); + return NULL; + } return (PyObject *)ret; } @@ -2362,7 +1806,7 @@ _is_default_descr(PyObject *descr, PyObject *typestr) { return 0; } name = PyTuple_GET_ITEM(tuple, 0); - if (!(PyUString_Check(name) && PyUString_GET_SIZE(name) == 0)) { + if (!(PyUnicode_Check(name) && PyUnicode_GetLength(name) == 0)) { return 0; } typestr2 = PyTuple_GET_ITEM(tuple, 1); @@ -3580,7 +3024,7 @@ array_from_text(PyArray_Descr *dtype, npy_intp num, char const *sep, size_t *nre npy_intp i; char *dptr, *clean_sep, *tmp; int err = 0; - int stop_reading_flag; /* -1 indicates end reached; -2 a parsing error */ + int stop_reading_flag = 0; /* -1 means end reached; -2 a parsing error */ npy_intp thisbuf = 0; npy_intp size; npy_intp bytes, totalbytes; diff --git a/numpy/core/src/multiarray/ctors.h b/numpy/core/src/multiarray/ctors.h index 9e63cd7d2..8db1412c7 100644 --- a/numpy/core/src/multiarray/ctors.h +++ b/numpy/core/src/multiarray/ctors.h @@ -30,13 +30,9 @@ PyArray_New( PyTypeObject *, int nd, npy_intp const *, int, npy_intp const*, void *, int, int, PyObject *); -NPY_NO_EXPORT int -PyArray_GetArrayParamsFromObject_int(PyObject *op, - PyArray_Descr *requested_dtype, - npy_bool writeable, - PyArray_Descr **out_dtype, - int *out_ndim, npy_intp *out_dims, - PyArrayObject **out_arr); +NPY_NO_EXPORT PyObject * +_array_from_array_like(PyObject *op, + PyArray_Descr *requested_dtype, npy_bool writeable, PyObject *context); NPY_NO_EXPORT PyObject * PyArray_FromAny(PyObject *op, PyArray_Descr *newtype, int min_depth, @@ -98,9 +94,6 @@ copy_and_swap(void *dst, void *src, int itemsize, npy_intp numitems, NPY_NO_EXPORT void byte_swap_vector(void *p, npy_intp n, int size); -NPY_NO_EXPORT int -PyArray_AssignFromSequence(PyArrayObject *self, PyObject *v); - /* * Calls arr_of_subclass.__array_wrap__(towrap), in order to make 'towrap' * have the same ndarray subclass as 'arr_of_subclass'. diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index cfe801898..8f3948c23 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -3429,7 +3429,7 @@ datetime_arange(PyObject *start, PyObject *stop, PyObject *step, * * Returns 0 on success, -1 on failure. */ -static int +NPY_NO_EXPORT int find_string_array_datetime64_type(PyArrayObject *arr, PyArray_DatetimeMetaData *meta) { @@ -3552,44 +3552,9 @@ fail: * Returns 0 on success, -1 on failure. */ static int -recursive_find_object_datetime64_type(PyObject *obj, - PyArray_DatetimeMetaData *meta) +find_object_datetime64_meta(PyObject *obj, PyArray_DatetimeMetaData *meta) { - /* Array -> use its metadata */ - if (PyArray_Check(obj)) { - PyArrayObject *arr = (PyArrayObject *)obj; - PyArray_Descr *arr_dtype = PyArray_DESCR(arr); - - if (arr_dtype->type_num == NPY_STRING || - arr_dtype->type_num == NPY_UNICODE) { - return find_string_array_datetime64_type(arr, meta); - } - /* If the array has metadata, use it */ - else if (arr_dtype->type_num == NPY_DATETIME || - arr_dtype->type_num == NPY_TIMEDELTA) { - PyArray_DatetimeMetaData *tmp_meta; - - /* Get the metadata from the type */ - tmp_meta = get_datetime_metadata_from_dtype(arr_dtype); - if (tmp_meta == NULL) { - return -1; - } - - /* Combine it with 'meta' */ - if (compute_datetime_metadata_greatest_common_divisor(meta, - tmp_meta, meta, 0, 0) < 0) { - return -1; - } - - return 0; - } - /* If it's not an object array, stop looking */ - else if (arr_dtype->type_num != NPY_OBJECT) { - return 0; - } - } - /* Datetime scalar -> use its metadata */ - else if (PyArray_IsScalar(obj, Datetime)) { + if (PyArray_IsScalar(obj, Datetime)) { PyDatetimeScalarObject *dts = (PyDatetimeScalarObject *)obj; /* Combine it with 'meta' */ @@ -3661,34 +3626,6 @@ recursive_find_object_datetime64_type(PyObject *obj, return 0; } - - /* Now check if what we have left is a sequence for recursion */ - if (PySequence_Check(obj)) { - Py_ssize_t i, len = PySequence_Size(obj); - if (len < 0 && PyErr_Occurred()) { - return -1; - } - - for (i = 0; i < len; ++i) { - int ret; - PyObject *f = PySequence_GetItem(obj, i); - if (f == NULL) { - return -1; - } - if (Npy_EnterRecursiveCall(" in recursive_find_object_datetime64_type") != 0) { - Py_DECREF(f); - return -1; - } - ret = recursive_find_object_datetime64_type(f, meta); - Py_LeaveRecursiveCall(); - Py_DECREF(f); - if (ret < 0) { - return ret; - } - } - - return 0; - } /* Otherwise ignore it */ else { return 0; @@ -3722,70 +3659,10 @@ delta_checker(PyArray_DatetimeMetaData *meta) * Returns 0 on success, -1 on failure. */ static int -recursive_find_object_timedelta64_type(PyObject *obj, - PyArray_DatetimeMetaData *meta) +find_object_timedelta64_meta(PyObject *obj, PyArray_DatetimeMetaData *meta) { - /* Array -> use its metadata */ - if (PyArray_Check(obj)) { - PyArrayObject *arr = (PyArrayObject *)obj; - PyArray_Descr *arr_dtype = PyArray_DESCR(arr); - - /* If the array has metadata, use it */ - if (arr_dtype->type_num == NPY_DATETIME || - arr_dtype->type_num == NPY_TIMEDELTA) { - PyArray_DatetimeMetaData *tmp_meta; - - /* Get the metadata from the type */ - tmp_meta = get_datetime_metadata_from_dtype(arr_dtype); - if (tmp_meta == NULL) { - return -1; - } - - /* Combine it with 'meta' */ - if (compute_datetime_metadata_greatest_common_divisor(meta, - tmp_meta, meta, 0, 0) < 0) { - return -1; - } - - return 0; - } - /* If it's not an object array, stop looking */ - else if (arr_dtype->type_num != NPY_OBJECT) { - return 0; - } - else { - if (PyArray_NDIM(arr) == 0) { - /* - * special handling of 0 dimensional NumPy object - * arrays, which may be indexed to retrieve their - * single object using [()], but not by using - * __getitem__(integer) approaches - */ - PyObject *item, *args; - - args = PyTuple_New(0); - if (args == NULL) { - return 0; - } - item = PyObject_GetItem(obj, args); - Py_DECREF(args); - if (item == NULL) { - return 0; - } - /* - * NOTE: may need other type checks here in the future - * for expanded 0 D datetime array conversions? - */ - if (PyDelta_Check(item)) { - Py_DECREF(item); - return delta_checker(meta); - } - Py_DECREF(item); - } - } - } /* Datetime scalar -> use its metadata */ - else if (PyArray_IsScalar(obj, Timedelta)) { + if (PyArray_IsScalar(obj, Timedelta)) { PyTimedeltaScalarObject *dts = (PyTimedeltaScalarObject *)obj; /* Combine it with 'meta' */ @@ -3805,34 +3682,6 @@ recursive_find_object_timedelta64_type(PyObject *obj, else if (PyDelta_Check(obj)) { return delta_checker(meta); } - - /* Now check if what we have left is a sequence for recursion */ - if (PySequence_Check(obj)) { - Py_ssize_t i, len = PySequence_Size(obj); - if (len < 0 && PyErr_Occurred()) { - return -1; - } - - for (i = 0; i < len; ++i) { - int ret; - PyObject *f = PySequence_GetItem(obj, i); - if (f == NULL) { - return -1; - } - if (Npy_EnterRecursiveCall(" in recursive_find_object_timedelta64_type") != 0) { - Py_DECREF(f); - return -1; - } - ret = recursive_find_object_timedelta64_type(f, meta); - Py_LeaveRecursiveCall(); - Py_DECREF(f); - if (ret < 0) { - return ret; - } - } - - return 0; - } /* Otherwise ignore it */ else { return 0; @@ -3853,7 +3702,7 @@ find_object_datetime_type(PyObject *obj, int type_num) meta.num = 1; if (type_num == NPY_DATETIME) { - if (recursive_find_object_datetime64_type(obj, &meta) < 0) { + if (find_object_datetime64_meta(obj, &meta) < 0) { return NULL; } else { @@ -3861,7 +3710,7 @@ find_object_datetime_type(PyObject *obj, int type_num) } } else if (type_num == NPY_TIMEDELTA) { - if (recursive_find_object_timedelta64_type(obj, &meta) < 0) { + if (find_object_timedelta64_meta(obj, &meta) < 0) { return NULL; } else { diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index 8d884bc00..67d57975b 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -469,7 +469,7 @@ _convert_from_array_descr(PyObject *obj, int align) /* Insert name into nameslist */ Py_INCREF(name); - if (PyUString_GET_SIZE(name) == 0) { + if (PyUnicode_GetLength(name) == 0) { Py_DECREF(name); if (title == NULL) { name = PyUString_FromFormat("f%d", i); @@ -478,7 +478,7 @@ _convert_from_array_descr(PyObject *obj, int align) } } /* On Py3, allow only non-empty Unicode strings as field names */ - else if (PyUString_Check(title) && PyUString_GET_SIZE(title) > 0) { + else if (PyUnicode_Check(title) && PyUnicode_GetLength(title) > 0) { name = title; Py_INCREF(name); } @@ -885,7 +885,17 @@ _try_convert_from_inherit_tuple(PyArray_Descr *type, PyObject *newobj) new->metadata = conv->metadata; Py_XINCREF(new->metadata); } - new->flags = conv->flags; + /* + * Certain flags must be inherited from the fields. This is needed + * only for void dtypes (or subclasses of it such as a record dtype). + * For other dtypes, the field part will only be used for direct field + * access and thus flag inheritance should not be necessary. + * (We only allow object fields if the dtype is object as well.) + * This ensures copying over of the NPY_FROM_FIELDS "inherited" flags. + */ + if (new->type_num == NPY_VOID) { + new->flags = conv->flags; + } Py_DECREF(conv); return new; @@ -1678,14 +1688,14 @@ _convert_from_str(PyObject *obj, int align) } /* Check for a deprecated Numeric-style typecode */ - char *dep_tps[] = {"Bool", "Complex", "Float", "Int", - "Object0", "String0", "Timedelta64", - "Unicode0", "UInt", "Void0"}; + /* `Uint` has deliberately weird uppercasing */ + char *dep_tps[] = {"Bytes", "Datetime64", "Str", "Uint"}; int ndep_tps = sizeof(dep_tps) / sizeof(dep_tps[0]); for (int i = 0; i < ndep_tps; ++i) { char *dep_tp = dep_tps[i]; if (strncmp(type, dep_tp, strlen(dep_tp)) == 0) { + /* Deprecated 2020-06-09, NumPy 1.20 */ if (DEPRECATE("Numeric-style type codes are " "deprecated and will result in " "an error in the future.") < 0) { @@ -1801,9 +1811,10 @@ static void arraydescr_dealloc(PyArray_Descr *self) { if (self->fields == Py_None) { - fprintf(stderr, "*** Reference count error detected: \n" \ - "an attempt was made to deallocate %d (%c) ***\n", + fprintf(stderr, "*** Reference count error detected: " + "an attempt was made to deallocate the dtype %d (%c) ***\n", self->type_num, self->type); + assert(0); Py_INCREF(self); Py_INCREF(self); return; diff --git a/numpy/core/src/multiarray/dtype_transfer.c b/numpy/core/src/multiarray/dtype_transfer.c index a26426d41..3a58b5849 100644 --- a/numpy/core/src/multiarray/dtype_transfer.c +++ b/numpy/core/src/multiarray/dtype_transfer.c @@ -1099,7 +1099,7 @@ get_datetime_to_unicode_transfer_function(int aligned, /* Get an ASCII string data type, adapted to match the UNICODE one */ str_dtype = PyArray_DescrFromType(NPY_STRING); - str_dtype = PyArray_AdaptFlexibleDType(NULL, dst_dtype, str_dtype); + str_dtype = PyArray_AdaptFlexibleDType(dst_dtype, str_dtype); if (str_dtype == NULL) { return NPY_FAIL; } @@ -1222,7 +1222,7 @@ get_unicode_to_datetime_transfer_function(int aligned, /* Get an ASCII string data type, adapted to match the UNICODE one */ str_dtype = PyArray_DescrFromType(NPY_STRING); - str_dtype = PyArray_AdaptFlexibleDType(NULL, src_dtype, str_dtype); + str_dtype = PyArray_AdaptFlexibleDType(src_dtype, str_dtype); if (str_dtype == NULL) { return NPY_FAIL; } diff --git a/numpy/core/src/multiarray/dtypemeta.c b/numpy/core/src/multiarray/dtypemeta.c index 9982cd676..3026e68e9 100644 --- a/numpy/core/src/multiarray/dtypemeta.c +++ b/numpy/core/src/multiarray/dtypemeta.c @@ -8,9 +8,13 @@ #define NPY_NO_DEPRECATED_API NPY_API_VERSION #define _MULTIARRAYMODULE #include <numpy/ndarraytypes.h> +#include <numpy/arrayscalars.h> #include "npy_pycompat.h" +#include "common.h" #include "dtypemeta.h" +#include "_datetime.h" +#include "array_coercion.h" static void @@ -104,6 +108,179 @@ legacy_dtype_default_new(PyArray_DTypeMeta *self, return (PyObject *)self->singleton; } + +static PyArray_Descr * +nonparametric_discover_descr_from_pyobject( + PyArray_DTypeMeta *cls, PyObject *obj) +{ + /* If the object is of the correct scalar type return our singleton */ + assert(!cls->parametric); + Py_INCREF(cls->singleton); + return cls->singleton; +} + + +static PyArray_Descr * +string_discover_descr_from_pyobject( + PyArray_DTypeMeta *cls, PyObject *obj) +{ + npy_intp itemsize = -1; + if (PyBytes_Check(obj)) { + itemsize = PyBytes_Size(obj); + } + else if (PyUnicode_Check(obj)) { + itemsize = PyUnicode_GetLength(obj); + } + if (itemsize != -1) { + if (cls->type_num == NPY_UNICODE) { + itemsize *= 4; + } + if (itemsize > NPY_MAX_INT) { + PyErr_SetString(PyExc_TypeError, + "string to large to store inside array."); + } + PyArray_Descr *res = PyArray_DescrNewFromType(cls->type_num); + res->elsize = (int)itemsize; + return res; + } + return PyArray_DTypeFromObjectStringDiscovery(obj, NULL, cls->type_num); +} + + +static PyArray_Descr * +void_discover_descr_from_pyobject( + PyArray_DTypeMeta *NPY_UNUSED(cls), PyObject *obj) +{ + if (PyArray_IsScalar(obj, Void)) { + PyVoidScalarObject *void_obj = (PyVoidScalarObject *)obj; + Py_INCREF(void_obj->descr); + return void_obj->descr; + } + if (PyBytes_Check(obj)) { + PyArray_Descr *descr = PyArray_DescrNewFromType(NPY_VOID); + Py_ssize_t itemsize = (int)PyBytes_Size(obj); + if (itemsize > NPY_MAX_INT) { + PyErr_SetString(PyExc_TypeError, + "byte-like to large to store inside array."); + } + descr->elsize = itemsize; + return descr; + } + PyErr_Format(PyExc_TypeError, + "A bytes-like object is required, not '%s'", Py_TYPE(obj)->tp_name); + return NULL; +} + + +static PyArray_Descr * +discover_datetime_and_timedelta_from_pyobject( + PyArray_DTypeMeta *cls, PyObject *obj) { + if (PyArray_IsScalar(obj, Datetime) || + PyArray_IsScalar(obj, Timedelta)) { + PyArray_DatetimeMetaData *meta; + PyArray_Descr *descr = PyArray_DescrFromScalar(obj); + meta = get_datetime_metadata_from_dtype(descr); + if (meta == NULL) { + return NULL; + } + PyArray_Descr *new_descr = create_datetime_dtype(cls->type_num, meta); + Py_DECREF(descr); + return new_descr; + } + else { + return find_object_datetime_type(obj, cls->type_num); + } +} + + +static PyArray_Descr * +flexible_default_descr(PyArray_DTypeMeta *cls) +{ + PyArray_Descr *res = PyArray_DescrNewFromType(cls->type_num); + if (res == NULL) { + return NULL; + } + res->elsize = 1; + if (cls->type_num == NPY_UNICODE) { + res->elsize *= 4; + } + return res; +} + + +static int +python_builtins_are_known_scalar_types( + PyArray_DTypeMeta *NPY_UNUSED(cls), PyTypeObject *pytype) +{ + /* + * Always accept the common Python types, this ensures that we do not + * convert pyfloat->float64->integers. Subclasses are hopefully rejected + * as being discovered. + * This is necessary only for python scalar classes which we discover + * as valid DTypes. + */ + if (pytype == &PyFloat_Type) { + return 1; + } + if (pytype == &PyLong_Type) { + return 1; + } + if (pytype == &PyBool_Type) { + return 1; + } + if (pytype == &PyComplex_Type) { + return 1; + } + if (pytype == &PyUnicode_Type) { + return 1; + } + if (pytype == &PyBytes_Type) { + return 1; + } + return 0; +} + + +static int +datetime_known_scalar_types( + PyArray_DTypeMeta *cls, PyTypeObject *pytype) +{ + if (python_builtins_are_known_scalar_types(cls, pytype)) { + return 1; + } + /* + * To be able to identify the descriptor from e.g. any string, datetime + * must take charge. Otherwise we would attempt casting which does not + * truly support this. Only object arrays are special cased in this way. + */ + return (PyType_IsSubtype(pytype, &PyString_Type) || + PyType_IsSubtype(pytype, &PyUnicode_Type)); +} + + +static int +string_known_scalar_types( + PyArray_DTypeMeta *cls, PyTypeObject *pytype) { + if (python_builtins_are_known_scalar_types(cls, pytype)) { + return 1; + } + if (PyType_IsSubtype(pytype, &PyDatetimeArrType_Type)) { + /* + * TODO: This should likely be deprecated or otherwise resolved. + * Deprecation has to occur in `String->setitem` unfortunately. + * + * Datetime currently do not cast to shorter strings, but string + * coercion for arbitrary values uses `str(obj)[:len]` so it works. + * This means `np.array(np.datetime64("2020-01-01"), "U9")` + * and `np.array(np.datetime64("2020-01-01")).astype("U9")` behave + * differently. + */ + return 1; + } + return 0; +} + + /** * This function takes a PyArray_Descr and replaces its base class with * a newly created dtype subclass (DTypeMeta instances). @@ -221,12 +398,41 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) dtype_class->f = descr->f; dtype_class->kind = descr->kind; + /* Strings and voids have (strange) logic around scalars. */ + dtype_class->is_known_scalar_type = python_builtins_are_known_scalar_types; + if (PyTypeNum_ISDATETIME(descr->type_num)) { /* Datetimes are flexible, but were not considered previously */ dtype_class->parametric = NPY_TRUE; + dtype_class->discover_descr_from_pyobject = ( + discover_datetime_and_timedelta_from_pyobject); + if (descr->type_num == NPY_DATETIME) { + dtype_class->is_known_scalar_type = datetime_known_scalar_types; + } } else if (PyTypeNum_ISFLEXIBLE(descr->type_num)) { dtype_class->parametric = NPY_TRUE; + dtype_class->default_descr = flexible_default_descr; + if (descr->type_num == NPY_VOID) { + dtype_class->discover_descr_from_pyobject = ( + void_discover_descr_from_pyobject); + } + else { + dtype_class->is_known_scalar_type = string_known_scalar_types; + dtype_class->discover_descr_from_pyobject = ( + string_discover_descr_from_pyobject); + } + } + else { + /* nonparametric case */ + dtype_class->discover_descr_from_pyobject = ( + nonparametric_discover_descr_from_pyobject); + } + + if (_PyArray_MapPyTypeToDType(dtype_class, descr->typeobj, + PyTypeNum_ISUSERDEF(dtype_class->type_num)) < 0) { + Py_DECREF(dtype_class); + return -1; } /* Finally, replace the current class of the descr */ diff --git a/numpy/core/src/multiarray/dtypemeta.h b/numpy/core/src/multiarray/dtypemeta.h index 97152d1ad..e0909a7eb 100644 --- a/numpy/core/src/multiarray/dtypemeta.h +++ b/numpy/core/src/multiarray/dtypemeta.h @@ -1,6 +1,8 @@ #ifndef _NPY_DTYPEMETA_H #define _NPY_DTYPEMETA_H +#define NPY_DTYPE(descr) ((PyArray_DTypeMeta *)Py_TYPE(descr)) + NPY_NO_EXPORT int dtypemeta_wrap_legacy_descriptor(PyArray_Descr *dtypem); diff --git a/numpy/core/src/multiarray/einsum.c.src b/numpy/core/src/multiarray/einsum.c.src index b914e5bb3..2538e05c6 100644 --- a/numpy/core/src/multiarray/einsum.c.src +++ b/numpy/core/src/multiarray/einsum.c.src @@ -31,9 +31,6 @@ #define EINSUM_USE_SSE1 0 #endif -/* - * TODO: Only some SSE2 for float64 is implemented. - */ #ifdef NPY_HAVE_SSE2_INTRINSICS #define EINSUM_USE_SSE2 1 #else @@ -276,6 +273,8 @@ static void #if EINSUM_USE_SSE1 && @float32@ __m128 a, b; +#elif EINSUM_USE_SSE2 && @float64@ + __m128d a, b; #endif NPY_EINSUM_DBG_PRINT1("@name@_sum_of_products_contig_two (%d)\n", @@ -319,6 +318,29 @@ finish_after_unrolled_loop: /* Finish off the loop */ goto finish_after_unrolled_loop; } +#elif EINSUM_USE_SSE2 && @float64@ + /* Use aligned instructions if possible */ + if (EINSUM_IS_SSE_ALIGNED(data0) && EINSUM_IS_SSE_ALIGNED(data1) && + EINSUM_IS_SSE_ALIGNED(data_out)) { + /* Unroll the loop by 8 */ + while (count >= 8) { + count -= 8; + +/**begin repeat2 + * #i = 0, 2, 4, 6# + */ + a = _mm_mul_pd(_mm_load_pd(data0+@i@), _mm_load_pd(data1+@i@)); + b = _mm_add_pd(a, _mm_load_pd(data_out+@i@)); + _mm_store_pd(data_out+@i@, b); +/**end repeat2**/ + data0 += 8; + data1 += 8; + data_out += 8; + } + + /* Finish off the loop */ + goto finish_after_unrolled_loop; + } #endif /* Unroll the loop by 8 */ @@ -333,6 +355,14 @@ finish_after_unrolled_loop: b = _mm_add_ps(a, _mm_loadu_ps(data_out+@i@)); _mm_storeu_ps(data_out+@i@, b); /**end repeat2**/ +#elif EINSUM_USE_SSE2 && @float64@ +/**begin repeat2 + * #i = 0, 2, 4, 6# + */ + a = _mm_mul_pd(_mm_loadu_pd(data0+@i@), _mm_loadu_pd(data1+@i@)); + b = _mm_add_pd(a, _mm_loadu_pd(data_out+@i@)); + _mm_storeu_pd(data_out+@i@, b); +/**end repeat2**/ #else /**begin repeat2 * #i = 0, 1, 2, 3, 4, 5, 6, 7# @@ -491,6 +521,8 @@ static void #if EINSUM_USE_SSE1 && @float32@ __m128 a, b, value1_sse; +#elif EINSUM_USE_SSE2 && @float64@ + __m128d a, b, value1_sse; #endif NPY_EINSUM_DBG_PRINT1("@name@_sum_of_products_contig_stride0_outcontig_two (%d)\n", @@ -534,6 +566,29 @@ finish_after_unrolled_loop: /* Finish off the loop */ goto finish_after_unrolled_loop; } +#elif EINSUM_USE_SSE2 && @float64@ + value1_sse = _mm_set1_pd(value1); + + /* Use aligned instructions if possible */ + if (EINSUM_IS_SSE_ALIGNED(data0) && EINSUM_IS_SSE_ALIGNED(data_out)) { + /* Unroll the loop by 8 */ + while (count >= 8) { + count -= 8; + +/**begin repeat2 + * #i = 0, 2, 4, 6# + */ + a = _mm_mul_pd(_mm_load_pd(data0+@i@), value1_sse); + b = _mm_add_pd(a, _mm_load_pd(data_out+@i@)); + _mm_store_pd(data_out+@i@, b); +/**end repeat2**/ + data0 += 8; + data_out += 8; + } + + /* Finish off the loop */ + goto finish_after_unrolled_loop; + } #endif /* Unroll the loop by 8 */ @@ -548,6 +603,14 @@ finish_after_unrolled_loop: b = _mm_add_ps(a, _mm_loadu_ps(data_out+@i@)); _mm_storeu_ps(data_out+@i@, b); /**end repeat2**/ +#elif EINSUM_USE_SSE2 && @float64@ +/**begin repeat2 + * #i = 0, 2, 4, 6# + */ + a = _mm_mul_pd(_mm_loadu_pd(data0+@i@), value1_sse); + b = _mm_add_pd(a, _mm_loadu_pd(data_out+@i@)); + _mm_storeu_pd(data_out+@i@, b); +/**end repeat2**/ #else /**begin repeat2 * #i = 0, 1, 2, 3, 4, 5, 6, 7# @@ -735,6 +798,8 @@ static void #if EINSUM_USE_SSE1 && @float32@ __m128 a, accum_sse = _mm_setzero_ps(); +#elif EINSUM_USE_SSE2 && @float64@ + __m128d a, accum_sse = _mm_setzero_pd(); #endif NPY_EINSUM_DBG_PRINT1("@name@_sum_of_products_stride0_contig_outstride0_two (%d)\n", @@ -772,15 +837,38 @@ finish_after_unrolled_loop: /**end repeat2**/ data1 += 8; } - -#if EINSUM_USE_SSE1 && @float32@ /* Add the four SSE values and put in accum */ a = _mm_shuffle_ps(accum_sse, accum_sse, _MM_SHUFFLE(2,3,0,1)); accum_sse = _mm_add_ps(a, accum_sse); a = _mm_shuffle_ps(accum_sse, accum_sse, _MM_SHUFFLE(1,0,3,2)); accum_sse = _mm_add_ps(a, accum_sse); _mm_store_ss(&accum, accum_sse); -#endif + + /* Finish off the loop */ + goto finish_after_unrolled_loop; + } +#elif EINSUM_USE_SSE2 && @float64@ + /* Use aligned instructions if possible */ + if (EINSUM_IS_SSE_ALIGNED(data1)) { + /* Unroll the loop by 8 */ + while (count >= 8) { + count -= 8; + +/**begin repeat2 + * #i = 0, 2, 4, 6# + */ + /* + * NOTE: This accumulation changes the order, so will likely + * produce slightly different results. + */ + accum_sse = _mm_add_pd(accum_sse, _mm_load_pd(data1+@i@)); +/**end repeat2**/ + data1 += 8; + } + /* Add the two SSE2 values and put in accum */ + a = _mm_shuffle_pd(accum_sse, accum_sse, _MM_SHUFFLE2(0,1)); + accum_sse = _mm_add_pd(a, accum_sse); + _mm_store_sd(&accum, accum_sse); /* Finish off the loop */ goto finish_after_unrolled_loop; @@ -801,6 +889,16 @@ finish_after_unrolled_loop: */ accum_sse = _mm_add_ps(accum_sse, _mm_loadu_ps(data1+@i@)); /**end repeat2**/ +#elif EINSUM_USE_SSE2 && @float64@ +/**begin repeat2 + * #i = 0, 2, 4, 6# + */ + /* + * NOTE: This accumulation changes the order, so will likely + * produce slightly different results. + */ + accum_sse = _mm_add_pd(accum_sse, _mm_loadu_pd(data1+@i@)); +/**end repeat2**/ #else /**begin repeat2 * #i = 0, 1, 2, 3, 4, 5, 6, 7# @@ -818,6 +916,11 @@ finish_after_unrolled_loop: a = _mm_shuffle_ps(accum_sse, accum_sse, _MM_SHUFFLE(1,0,3,2)); accum_sse = _mm_add_ps(a, accum_sse); _mm_store_ss(&accum, accum_sse); +#elif EINSUM_USE_SSE2 && @float64@ + /* Add the two SSE2 values and put in accum */ + a = _mm_shuffle_pd(accum_sse, accum_sse, _MM_SHUFFLE2(0,1)); + accum_sse = _mm_add_pd(a, accum_sse); + _mm_store_sd(&accum, accum_sse); #endif /* Finish off the loop */ @@ -834,6 +937,8 @@ static void #if EINSUM_USE_SSE1 && @float32@ __m128 a, accum_sse = _mm_setzero_ps(); +#elif EINSUM_USE_SSE2 && @float64@ + __m128d a, accum_sse = _mm_setzero_pd(); #endif NPY_EINSUM_DBG_PRINT1("@name@_sum_of_products_contig_stride0_outstride0_two (%d)\n", @@ -871,16 +976,37 @@ finish_after_unrolled_loop: /**end repeat2**/ data0 += 8; } - -#if EINSUM_USE_SSE1 && @float32@ /* Add the four SSE values and put in accum */ a = _mm_shuffle_ps(accum_sse, accum_sse, _MM_SHUFFLE(2,3,0,1)); accum_sse = _mm_add_ps(a, accum_sse); a = _mm_shuffle_ps(accum_sse, accum_sse, _MM_SHUFFLE(1,0,3,2)); accum_sse = _mm_add_ps(a, accum_sse); _mm_store_ss(&accum, accum_sse); -#endif + /* Finish off the loop */ + goto finish_after_unrolled_loop; + } +#elif EINSUM_USE_SSE2 && @float64@ + /* Use aligned instructions if possible */ + if (EINSUM_IS_SSE_ALIGNED(data0)) { + /* Unroll the loop by 8 */ + while (count >= 8) { + count -= 8; +/**begin repeat2 + * #i = 0, 2, 4, 6# + */ + /* + * NOTE: This accumulation changes the order, so will likely + * produce slightly different results. + */ + accum_sse = _mm_add_pd(accum_sse, _mm_load_pd(data0+@i@)); +/**end repeat2**/ + data0 += 8; + } + /* Add the two SSE2 values and put in accum */ + a = _mm_shuffle_pd(accum_sse, accum_sse, _MM_SHUFFLE2(0,1)); + accum_sse = _mm_add_pd(a, accum_sse); + _mm_store_sd(&accum, accum_sse); /* Finish off the loop */ goto finish_after_unrolled_loop; } @@ -900,6 +1026,16 @@ finish_after_unrolled_loop: */ accum_sse = _mm_add_ps(accum_sse, _mm_loadu_ps(data0+@i@)); /**end repeat2**/ +#elif EINSUM_USE_SSE2 && @float64@ +/**begin repeat2 + * #i = 0, 2, 4, 6# + */ + /* + * NOTE: This accumulation changes the order, so will likely + * produce slightly different results. + */ + accum_sse = _mm_add_pd(accum_sse, _mm_loadu_pd(data0+@i@)); +/**end repeat2**/ #else /**begin repeat2 * #i = 0, 1, 2, 3, 4, 5, 6, 7# @@ -917,6 +1053,11 @@ finish_after_unrolled_loop: a = _mm_shuffle_ps(accum_sse, accum_sse, _MM_SHUFFLE(1,0,3,2)); accum_sse = _mm_add_ps(a, accum_sse); _mm_store_ss(&accum, accum_sse); +#elif EINSUM_USE_SSE2 && @float64@ + /* Add the two SSE2 values and put in accum */ + a = _mm_shuffle_pd(accum_sse, accum_sse, _MM_SHUFFLE2(0,1)); + accum_sse = _mm_add_pd(a, accum_sse); + _mm_store_sd(&accum, accum_sse); #endif /* Finish off the loop */ diff --git a/numpy/core/src/multiarray/item_selection.c b/numpy/core/src/multiarray/item_selection.c index 45c019f49..8052e24e4 100644 --- a/numpy/core/src/multiarray/item_selection.c +++ b/numpy/core/src/multiarray/item_selection.c @@ -26,7 +26,7 @@ #include "npy_binsearch.h" #include "alloc.h" #include "arraytypes.h" - +#include "array_coercion.h" static NPY_GCC_OPT_3 NPY_INLINE int @@ -2629,5 +2629,5 @@ PyArray_MultiIndexSetItem(PyArrayObject *self, const npy_intp *multi_index, data += ind * strides[idim]; } - return PyArray_SETITEM(self, data, obj); + return PyArray_Pack(PyArray_DESCR(self), data, obj); } diff --git a/numpy/core/src/multiarray/iterators.c b/numpy/core/src/multiarray/iterators.c index c71b7b770..ac5b90400 100644 --- a/numpy/core/src/multiarray/iterators.c +++ b/numpy/core/src/multiarray/iterators.c @@ -15,6 +15,7 @@ #include "iterators.h" #include "ctors.h" #include "common.h" +#include "array_coercion.h" #define NEWAXIS_INDEX -1 #define ELLIPSIS_INDEX -2 @@ -824,7 +825,7 @@ iter_ass_subscript(PyArrayIterObject *self, PyObject *ind, PyObject *val) if (PyBool_Check(ind)) { retval = 0; if (PyObject_IsTrue(ind)) { - retval = PyArray_SETITEM(self->ao, self->dataptr, val); + retval = PyArray_Pack(PyArray_DESCR(self->ao), self->dataptr, val); } goto finish; } @@ -841,7 +842,7 @@ iter_ass_subscript(PyArrayIterObject *self, PyObject *ind, PyObject *val) goto finish; } PyArray_ITER_GOTO1D(self, start); - retval = type->f->setitem(val, self->dataptr, self->ao); + retval = PyArray_Pack(PyArray_DESCR(self->ao), self->dataptr, val); PyArray_ITER_RESET(self); if (retval < 0) { PyErr_SetString(PyExc_ValueError, diff --git a/numpy/core/src/multiarray/mapping.c b/numpy/core/src/multiarray/mapping.c index 7aefbfc38..c27e0c391 100644 --- a/numpy/core/src/multiarray/mapping.c +++ b/numpy/core/src/multiarray/mapping.c @@ -22,6 +22,7 @@ #include "item_selection.h" #include "mem_overlap.h" #include "array_assign.h" +#include "array_coercion.h" #define HAS_INTEGER 1 @@ -1754,7 +1755,7 @@ array_assign_item(PyArrayObject *self, Py_ssize_t i, PyObject *op) if (get_item_pointer(self, &item, indices, 1) < 0) { return -1; } - if (PyArray_SETITEM(self, item, op) < 0) { + if (PyArray_Pack(PyArray_DESCR(self), item, op) < 0) { return -1; } } @@ -1832,7 +1833,7 @@ array_assign_subscript(PyArrayObject *self, PyObject *ind, PyObject *op) if (get_item_pointer(self, &item, indices, index_num) < 0) { return -1; } - if (PyArray_SETITEM(self, item, op) < 0) { + if (PyArray_Pack(PyArray_DESCR(self), item, op) < 0) { return -1; } /* integers do not store objects in indices */ @@ -2480,8 +2481,6 @@ PyArray_MapIterCheckIndices(PyArrayMapIterObject *mit) int i; NPY_BEGIN_THREADS_DEF; - intp_type = PyArray_DescrFromType(NPY_INTP); - if (NpyIter_GetIterSize(mit->outer) == 0) { /* * When the outer iteration is empty, the indices broadcast to an @@ -2493,6 +2492,8 @@ PyArray_MapIterCheckIndices(PyArrayMapIterObject *mit) return 0; } + intp_type = PyArray_DescrFromType(NPY_INTP); + NPY_BEGIN_THREADS; for (i=0; i < mit->numiter; i++) { diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index 262514ec6..a2db8042f 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -14,6 +14,7 @@ #include "npy_pycompat.h" #include "npy_import.h" #include "ufunc_override.h" +#include "array_coercion.h" #include "common.h" #include "templ_common.h" /* for npy_mul_with_overflow_intp */ #include "ctors.h" @@ -735,6 +736,7 @@ array_setscalar(PyArrayObject *self, PyObject *args) else { PyErr_SetString(PyExc_ValueError, "can only convert an array of size 1 to a Python scalar"); + return NULL; } } /* Special case of C-order flat indexing... :| */ @@ -808,6 +810,12 @@ array_astype(PyArrayObject *self, PyObject *args, PyObject *kwds) return NULL; } + /* If it is not a concrete dtype instance find the best one for the array */ + Py_SETREF(dtype, PyArray_AdaptDescriptorToArray(self, (PyObject *)dtype)); + if (dtype == NULL) { + return NULL; + } + /* * If the memory layout matches and, data types are equivalent, * and it's not a subtype if subok is False, then we @@ -830,13 +838,6 @@ array_astype(PyArrayObject *self, PyObject *args, PyObject *kwds) else if (PyArray_CanCastArrayTo(self, dtype, casting)) { PyArrayObject *ret; - /* If the requested dtype is flexible, adapt it */ - dtype = PyArray_AdaptFlexibleDType((PyObject *)self, - PyArray_DESCR(self), dtype); - if (dtype == NULL) { - return NULL; - } - /* This steals the reference to dtype, so no DECREF of dtype */ ret = (PyArrayObject *)PyArray_NewLikeArray( self, order, dtype, subok); diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index 614930bdc..7c5ceb962 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -35,6 +35,8 @@ NPY_NO_EXPORT int NPY_NUMUSERTYPES = 0; /* Internal APIs */ #include "alloc.h" +#include "abstractdtypes.h" +#include "array_coercion.h" #include "arrayfunction_override.h" #include "arraytypes.h" #include "arrayobject.h" @@ -823,6 +825,9 @@ PyArray_InnerProduct(PyObject *op1, PyObject *op2) PyObject* ret = NULL; typenum = PyArray_ObjectType(op1, 0); + if (typenum == NPY_NOTYPE && PyErr_Occurred()) { + return NULL; + } typenum = PyArray_ObjectType(op2, typenum); typec = PyArray_DescrFromType(typenum); if (typec == NULL) { @@ -912,6 +917,9 @@ PyArray_MatrixProduct2(PyObject *op1, PyObject *op2, PyArrayObject* out) NPY_BEGIN_THREADS_DEF; typenum = PyArray_ObjectType(op1, 0); + if (typenum == NPY_NOTYPE && PyErr_Occurred()) { + return NULL; + } typenum = PyArray_ObjectType(op2, typenum); typec = PyArray_DescrFromType(typenum); if (typec == NULL) { @@ -3945,6 +3953,7 @@ normalize_axis_index(PyObject *NPY_UNUSED(self), PyObject *args, PyObject *kwds) return PyInt_FromLong(axis); } + static struct PyMethodDef array_module_methods[] = { {"_get_implementing_args", (PyCFunction)array__get_implementing_args, @@ -4118,6 +4127,8 @@ static struct PyMethodDef array_module_methods[] = { METH_VARARGS | METH_KEYWORDS, NULL}, {"set_legacy_print_mode", (PyCFunction)set_legacy_print_mode, METH_VARARGS, NULL}, + {"_discover_array_parameters", (PyCFunction)_discover_array_parameters, + METH_VARARGS | METH_KEYWORDS, NULL}, /* from umath */ {"frompyfunc", (PyCFunction) ufunc_frompyfunc, @@ -4509,6 +4520,26 @@ PyMODINIT_FUNC PyInit__multiarray_umath(void) { } Py_DECREF(s); + s = npy_cpu_baseline_list(); + if (s == NULL) { + goto err; + } + if (PyDict_SetItemString(d, "__cpu_baseline__", s) < 0) { + Py_DECREF(s); + goto err; + } + Py_DECREF(s); + + s = npy_cpu_dispatch_list(); + if (s == NULL) { + goto err; + } + if (PyDict_SetItemString(d, "__cpu_dispatch__", s) < 0) { + Py_DECREF(s); + goto err; + } + Py_DECREF(s); + s = NpyCapsule_FromVoidPtr((void *)_datetime_strings, NULL); if (s == NULL) { goto err; @@ -4567,6 +4598,9 @@ PyMODINIT_FUNC PyInit__multiarray_umath(void) { if (set_typeinfo(d) != 0) { goto err; } + if (initialize_and_map_pytypes_to_dtypes() < 0) { + goto err; + } if (initumath(m) != 0) { goto err; } diff --git a/numpy/core/src/multiarray/nditer_constr.c b/numpy/core/src/multiarray/nditer_constr.c index f2dbc9f03..7da17eafe 100644 --- a/numpy/core/src/multiarray/nditer_constr.c +++ b/numpy/core/src/multiarray/nditer_constr.c @@ -16,6 +16,7 @@ #include "nditer_impl.h" #include "arrayobject.h" +#include "array_coercion.h" #include "templ_common.h" #include "array_assign.h" @@ -1101,17 +1102,11 @@ npyiter_prepare_one_operand(PyArrayObject **op, */ if (op_request_dtype != NULL) { /* We just have a borrowed reference to op_request_dtype */ - Py_INCREF(op_request_dtype); - /* If the requested dtype is flexible, adapt it */ - op_request_dtype = PyArray_AdaptFlexibleDType((PyObject *)(*op), PyArray_DESCR(*op), - op_request_dtype); - if (op_request_dtype == NULL) { + Py_SETREF(*op_dtype, PyArray_AdaptDescriptorToArray( + *op, (PyObject *)op_request_dtype)); + if (*op_dtype == NULL) { return 0; } - - /* Store the requested dtype */ - Py_DECREF(*op_dtype); - *op_dtype = op_request_dtype; } /* Check if the operand is in the byte order requested */ diff --git a/numpy/core/src/multiarray/scalarapi.c b/numpy/core/src/multiarray/scalarapi.c index f3c440dc6..6f3d102a4 100644 --- a/numpy/core/src/multiarray/scalarapi.c +++ b/numpy/core/src/multiarray/scalarapi.c @@ -69,7 +69,7 @@ scalar_value(PyObject *scalar, PyArray_Descr *descr) CASE(TIMEDELTA, Timedelta); #undef CASE case NPY_STRING: - return (void *)PyString_AS_STRING(scalar); + return (void *)PyBytes_AsString(scalar); case NPY_UNICODE: /* lazy initialization, to reduce the memory used by string scalars */ if (PyArrayScalar_VAL(scalar, Unicode) == NULL) { @@ -141,7 +141,18 @@ scalar_value(PyObject *scalar, PyArray_Descr *descr) return (void *)PyString_AS_STRING(scalar); } if (_CHK(Unicode)) { - return (void *)PyUnicode_AS_DATA(scalar); + /* Treat this the same as the NPY_UNICODE base class */ + + /* lazy initialization, to reduce the memory used by string scalars */ + if (PyArrayScalar_VAL(scalar, Unicode) == NULL) { + Py_UCS4 *raw_data = PyUnicode_AsUCS4Copy(scalar); + if (raw_data == NULL) { + return NULL; + } + PyArrayScalar_VAL(scalar, Unicode) = raw_data; + return (void *)raw_data; + } + return PyArrayScalar_VAL(scalar, Unicode); } if (_CHK(Void)) { /* Note: no & needed here, so can't use _IFCASE */ @@ -286,14 +297,10 @@ PyArray_CastScalarDirect(PyObject *scalar, PyArray_Descr *indescr, NPY_NO_EXPORT PyObject * PyArray_FromScalar(PyObject *scalar, PyArray_Descr *outcode) { - PyArray_Descr *typecode; - PyArrayObject *r; - char *memptr; - PyObject *ret; - /* convert to 0-dim array of scalar typecode */ - typecode = PyArray_DescrFromScalar(scalar); + PyArray_Descr *typecode = PyArray_DescrFromScalar(scalar); if (typecode == NULL) { + Py_XDECREF(outcode); return NULL; } if ((typecode->type_num == NPY_VOID) && @@ -307,49 +314,53 @@ PyArray_FromScalar(PyObject *scalar, PyArray_Descr *outcode) NULL, (PyObject *)scalar); } - /* Need to INCREF typecode because PyArray_NewFromDescr steals a - * reference below and we still need to access typecode afterwards. */ - Py_INCREF(typecode); - r = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, + PyArrayObject *r = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, typecode, 0, NULL, NULL, NULL, 0, NULL); - if (r==NULL) { - Py_DECREF(typecode); Py_XDECREF(outcode); + if (r == NULL) { + Py_XDECREF(outcode); return NULL; } + /* the dtype used by the array may be different to the one requested */ + typecode = PyArray_DESCR(r); if (PyDataType_FLAGCHK(typecode, NPY_USE_SETITEM)) { if (typecode->f->setitem(scalar, PyArray_DATA(r), r) < 0) { - Py_DECREF(typecode); Py_XDECREF(outcode); Py_DECREF(r); + Py_DECREF(r); + Py_XDECREF(outcode); return NULL; } - goto finish; } + else { + char *memptr = scalar_value(scalar, typecode); - memptr = scalar_value(scalar, typecode); - - memcpy(PyArray_DATA(r), memptr, PyArray_ITEMSIZE(r)); - if (PyDataType_FLAGCHK(typecode, NPY_ITEM_HASOBJECT)) { - /* Need to INCREF just the PyObject portion */ - PyArray_Item_INCREF(memptr, typecode); + memcpy(PyArray_DATA(r), memptr, PyArray_ITEMSIZE(r)); + if (PyDataType_FLAGCHK(typecode, NPY_ITEM_HASOBJECT)) { + /* Need to INCREF just the PyObject portion */ + PyArray_Item_INCREF(memptr, typecode); + } } -finish: if (outcode == NULL) { - Py_DECREF(typecode); return (PyObject *)r; } if (PyArray_EquivTypes(outcode, typecode)) { if (!PyTypeNum_ISEXTENDED(typecode->type_num) || (outcode->elsize == typecode->elsize)) { - Py_DECREF(typecode); Py_DECREF(outcode); + /* + * Since the type is equivalent, and we haven't handed the array + * to anyone yet, let's fix the dtype to be what was requested, + * even if it is equivalent to what was passed in. + */ + Py_SETREF(((PyArrayObject_fields *)r)->descr, outcode); + return (PyObject *)r; } } /* cast if necessary to desired output typecode */ - ret = PyArray_CastToType((PyArrayObject *)r, outcode, 0); - Py_DECREF(typecode); Py_DECREF(r); + PyObject *ret = PyArray_CastToType(r, outcode, 0); + Py_DECREF(r); return ret; } diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src index f925a4795..088b380aa 100644 --- a/numpy/core/src/multiarray/scalartypes.c.src +++ b/numpy/core/src/multiarray/scalartypes.c.src @@ -341,47 +341,68 @@ format_@name@(@type@ val, npy_bool scientific, /**end repeat**/ /* - * over-ride repr and str of array-scalar strings and unicode to - * remove NULL bytes and then call the corresponding functions - * of string and unicode. - * - * FIXME: - * is this really a good idea? - * stop using Py_UNICODE here. + * Over-ride repr and str of array-scalar byte strings to remove NULL bytes and + * then call the corresponding functions of PyBytes_Type to generate the string */ /**begin repeat - * #name = string*2,unicode*2# - * #form = (repr,str)*2# - * #Name = String*2,Unicode*2# - * #NAME = STRING*2,UNICODE*2# - * #extra = AndSize*2,,# - * #type = npy_char*2, Py_UNICODE*2# + * #form = repr, str# */ static PyObject * -@name@type_@form@(PyObject *self) +stringtype_@form@(PyObject *self) { - const @type@ *dptr, *ip; - int len; + const npy_char *dptr, *ip; + Py_ssize_t len; PyObject *new; PyObject *ret; - ip = dptr = Py@Name@_AS_@NAME@(self); - len = Py@Name@_GET_SIZE(self); - dptr += len-1; - while(len > 0 && *dptr-- == 0) { - len--; - } - new = Py@Name@_From@Name@@extra@(ip, len); + ip = PyBytes_AS_STRING(self); + len = PyBytes_GET_SIZE(self); + for(dptr = ip + len - 1; len > 0 && *dptr == 0; len--, dptr--); + new = PyBytes_FromStringAndSize(ip, len); if (new == NULL) { - return PyUString_FromString(""); + return NULL; } - ret = Py@Name@_Type.tp_@form@(new); + ret = PyBytes_Type.tp_@form@(new); Py_DECREF(new); return ret; } /**end repeat**/ +/* + * Over-ride repr and str of array-scalar strings to remove NULL code points and + * then call the corresponding functions of PyUnicode_Type to generate the string + */ + +/**begin repeat + * #form = repr, str# + */ +static PyObject * +unicodetype_@form@(PyObject *self) +{ + Py_UCS4 *dptr, *ip; + Py_ssize_t len; + PyObject *new; + PyObject *ret; + + /* PyUnicode_READY is called by PyUnicode_GetLength */ + len = PyUnicode_GetLength(self); + ip = PyUnicode_AsUCS4Copy(self); + if (ip == NULL) { + return NULL; + } + for(dptr = ip + len - 1; len > 0 && *dptr == 0; len--, dptr--); + new = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, ip, len); + if (new == NULL) { + PyMem_Free(ip); + return NULL; + } + ret = PyUnicode_Type.tp_@form@(new); + Py_DECREF(new); + PyMem_Free(ip); + return ret; +} +/**end repeat**/ /* * Convert array of bytes to a string representation much like bytes.__repr__, @@ -719,12 +740,13 @@ legacy_@name@_format@kind@(@type@ val) return NULL; } if (!npy_isfinite(val.imag)) { - strncat(buf, "*", 1); + strncat(buf, "*", sizeof(buf) - strlen(buf) - 1); } - strncat(buf, "j", 1); + strncat(buf, "j", sizeof(buf) - strlen(buf) - 1); } else { char re[64], im[64]; + if (npy_isfinite(val.real)) { PyOS_snprintf(format, sizeof(format), _FMT1, @NAME@PREC_@KIND@); res = NumPyOS_ascii_format@suff@(re, sizeof(re), format, @@ -767,7 +789,7 @@ legacy_@name@_format@kind@(@type@ val) strcpy(im, "-inf"); } if (!npy_isfinite(val.imag)) { - strncat(im, "*", 1); + strncat(im, "*", sizeof(im) - strlen(im) - 1); } } PyOS_snprintf(buf, sizeof(buf), "(%s%sj)", re, im); diff --git a/numpy/core/src/multiarray/sequence.c b/numpy/core/src/multiarray/sequence.c index 1efdd204f..1c74f1719 100644 --- a/numpy/core/src/multiarray/sequence.c +++ b/numpy/core/src/multiarray/sequence.c @@ -50,9 +50,24 @@ array_contains(PyArrayObject *self, PyObject *el) return ret; } +static PyObject * +array_concat(PyObject *self, PyObject *other) +{ + /* + * Throw a type error, when trying to concat NDArrays + * NOTE: This error is not Thrown when running with PyPy + */ + PyErr_SetString(PyExc_TypeError, + "Concatenation operation is not implemented for NumPy arrays, " + "use np.concatenate() instead. Please do not rely on this error; " + "it may not be given on all Python implementations."); + return NULL; +} + + NPY_NO_EXPORT PySequenceMethods array_as_sequence = { (lenfunc)array_length, /*sq_length*/ - (binaryfunc)NULL, /*sq_concat is handled by nb_add*/ + (binaryfunc)array_concat, /*sq_concat for operator.concat*/ (ssizeargfunc)NULL, (ssizeargfunc)array_item, (ssizessizeargfunc)NULL, diff --git a/numpy/core/src/npymath/ieee754.c.src b/numpy/core/src/npymath/ieee754.c.src index 3f66b24a4..4e6ddb712 100644 --- a/numpy/core/src/npymath/ieee754.c.src +++ b/numpy/core/src/npymath/ieee754.c.src @@ -634,7 +634,7 @@ void npy_set_floatstatus_invalid(void) fpsetsticky(FP_X_INV); } -#elif defined(_AIX) +#elif defined(_AIX) && !defined(__GNUC__) #include <float.h> #include <fpxcp.h> diff --git a/numpy/core/src/umath/_umath_tests.c.src b/numpy/core/src/umath/_umath_tests.c.src index abc8d78c4..d08aabd64 100644 --- a/numpy/core/src/umath/_umath_tests.c.src +++ b/numpy/core/src/umath/_umath_tests.c.src @@ -576,6 +576,51 @@ fail: return NULL; } +// Testing the utilites of the CPU dispatcher +#ifndef NPY_DISABLE_OPTIMIZATION + #include "_umath_tests.dispatch.h" +#endif +NPY_CPU_DISPATCH_DECLARE(extern const char *_umath_tests_dispatch_var) +NPY_CPU_DISPATCH_DECLARE(const char *_umath_tests_dispatch_func, (void)) +NPY_CPU_DISPATCH_DECLARE(void _umath_tests_dispatch_attach, (PyObject *list)) + +static PyObject * +UMath_Tests_test_dispatch(PyObject *NPY_UNUSED(dummy), PyObject *NPY_UNUSED(dummy2)) +{ + const char *highest_func, *highest_var; + NPY_CPU_DISPATCH_CALL(highest_func = _umath_tests_dispatch_func, ()) + NPY_CPU_DISPATCH_CALL(highest_var = _umath_tests_dispatch_var) + const char *highest_func_xb = "nobase", *highest_var_xb = "nobase"; + NPY_CPU_DISPATCH_CALL_XB(highest_func_xb = _umath_tests_dispatch_func, ()) + NPY_CPU_DISPATCH_CALL_XB(highest_var_xb = _umath_tests_dispatch_var) + + PyObject *dict = PyDict_New(), *item; + if (dict == NULL) { + return NULL; + } + /**begin repeat + * #str = func, var, func_xb, var_xb# + */ + item = PyUnicode_FromString(highest_@str@); + if (item == NULL || PyDict_SetItemString(dict, "@str@", item) < 0) { + goto err; + } + /**end repeat**/ + item = PyList_New(0); + if (item == NULL || PyDict_SetItemString(dict, "all", item) < 0) { + goto err; + } + NPY_CPU_DISPATCH_CALL_ALL(_umath_tests_dispatch_attach, (item)) + if (PyErr_Occurred()) { + goto err; + } + return dict; +err: + Py_XDECREF(item); + Py_DECREF(dict); + return NULL; +} + static PyMethodDef UMath_TestsMethods[] = { {"test_signature", UMath_Tests_test_signature, METH_VARARGS, "Test signature parsing of ufunc. \n" @@ -583,6 +628,7 @@ static PyMethodDef UMath_TestsMethods[] = { "If fails, it returns NULL. Otherwise it returns a tuple of ufunc " "internals. \n", }, + {"test_dispatch", UMath_Tests_test_dispatch, METH_NOARGS, NULL}, {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -604,6 +650,11 @@ PyMODINIT_FUNC PyInit__umath_tests(void) { PyObject *d; PyObject *version; + // Initialize CPU features + if (npy_cpu_init() < 0) { + return NULL; + } + m = PyModule_Create(&moduledef); if (m == NULL) { return NULL; @@ -632,6 +683,5 @@ PyMODINIT_FUNC PyInit__umath_tests(void) { "cannot load _umath_tests module."); return NULL; } - return m; } diff --git a/numpy/core/src/umath/_umath_tests.dispatch.c b/numpy/core/src/umath/_umath_tests.dispatch.c new file mode 100644 index 000000000..d86a54411 --- /dev/null +++ b/numpy/core/src/umath/_umath_tests.dispatch.c @@ -0,0 +1,33 @@ +/** + * Testing the utilites of the CPU dispatcher + * + * @targets $werror baseline + * SSE2 SSE41 AVX2 + * VSX VSX2 VSX3 + * NEON ASIMD ASIMDHP + */ +#include <Python.h> +#include "npy_cpu_dispatch.h" + +#ifndef NPY_DISABLE_OPTIMIZATION + #include "_umath_tests.dispatch.h" +#endif + +NPY_CPU_DISPATCH_DECLARE(const char *_umath_tests_dispatch_func, (void)) +NPY_CPU_DISPATCH_DECLARE(extern const char *_umath_tests_dispatch_var) +NPY_CPU_DISPATCH_DECLARE(void _umath_tests_dispatch_attach, (PyObject *list)) + +const char *NPY_CPU_DISPATCH_CURFX(_umath_tests_dispatch_var) = NPY_TOSTRING(NPY_CPU_DISPATCH_CURFX(var)); +const char *NPY_CPU_DISPATCH_CURFX(_umath_tests_dispatch_func)(void) +{ + static const char *current = NPY_TOSTRING(NPY_CPU_DISPATCH_CURFX(func)); + return current; +} + +void NPY_CPU_DISPATCH_CURFX(_umath_tests_dispatch_attach)(PyObject *list) +{ + PyObject *item = PyUnicode_FromString(NPY_TOSTRING(NPY_CPU_DISPATCH_CURFX(func))); + if (item) { + PyList_Append(list, item); + } +} diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 0cfa1cea7..d9591ab33 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -1558,6 +1558,14 @@ DOUBLE_exp(char **args, npy_intp const *dimensions, npy_intp const *steps, void *(npy_double *)op1 = npy_exp(in1); } } +NPY_NO_EXPORT NPY_GCC_OPT_3 void +DOUBLE_log(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(data)) +{ + UNARY_LOOP { + const npy_double in1 = *(npy_double *)ip1; + *(npy_double *)op1 = npy_log(in1); + } +} /**begin repeat * #isa = avx512f, fma# @@ -1700,6 +1708,16 @@ DOUBLE_exp_avx512f(char **args, npy_intp const *dimensions, npy_intp const *step } } +NPY_NO_EXPORT NPY_GCC_OPT_3 void +DOUBLE_log_avx512f(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(data)) +{ + if (!run_unary_avx512f_log_DOUBLE(args, dimensions, steps)) { + UNARY_LOOP { + const npy_double in1 = *(npy_double *)ip1; + *(npy_double *)op1 = npy_log(in1); + } + } +} /**begin repeat * Float types diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src index 5dd49c465..6b8b77e99 100644 --- a/numpy/core/src/umath/loops.h.src +++ b/numpy/core/src/umath/loops.h.src @@ -202,6 +202,12 @@ DOUBLE_exp(char **args, npy_intp const *dimensions, npy_intp const *steps, void NPY_NO_EXPORT void DOUBLE_exp_avx512f(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +DOUBLE_log(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void +DOUBLE_log_avx512f(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func)); + /**begin repeat * #func = sin, cos, exp, log# */ diff --git a/numpy/core/src/umath/matmul.c.src b/numpy/core/src/umath/matmul.c.src index 5cbb6e94d..0e47d1ab5 100644 --- a/numpy/core/src/umath/matmul.c.src +++ b/numpy/core/src/umath/matmul.c.src @@ -27,6 +27,7 @@ ***************************************************************************** */ +#if defined(HAVE_CBLAS) /* * -1 to be conservative, in case blas internally uses a for loop with an * inclusive upper bound @@ -61,7 +62,6 @@ is_blasable2d(npy_intp byte_stride1, npy_intp byte_stride2, return NPY_FALSE; } -#if defined(HAVE_CBLAS) static const npy_cdouble oneD = {1.0, 0.0}, zeroD = {0.0, 0.0}; static const npy_cfloat oneF = {1.0, 0.0}, zeroF = {0.0, 0.0}; diff --git a/numpy/core/src/umath/npy_simd_data.h b/numpy/core/src/umath/npy_simd_data.h index 36c8b6c03..45487d0a8 100644 --- a/numpy/core/src/umath/npy_simd_data.h +++ b/numpy/core/src/umath/npy_simd_data.h @@ -1,6 +1,7 @@ #ifndef __NPY_SIMD_DATA_H_ #define __NPY_SIMD_DATA_H_ #if defined HAVE_ATTRIBUTE_TARGET_AVX512F_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS +#if !(defined(__clang__) && (__clang_major__ < 10 || (__clang_major__ == 10 && __clang_minor__ < 1))) /* * Constants used in vector implementation of float64 exp(x) */ @@ -85,6 +86,7 @@ static npy_uint64 EXP_Table_tail[32] = { 0x3C99D3E12DD8A18B, }; #endif +#endif /* * Constants used in vector implementation of exp(x) @@ -134,4 +136,156 @@ static npy_uint64 EXP_Table_tail[32] = { #define NPY_COEFF_INVF7_SINEf -0x1.a06bbap-13f #define NPY_COEFF_INVF9_SINEf 0x1.7d3bbcp-19f +/* + * Lookup table of log(c_k) + * Reference form: Tang, Ping-Tak Peter. "Table-driven implementation of the + * logarithm function in IEEE floating-point arithmetic." ACM Transactions + * on Mathematical Software (TOMS) 16.4 (1990): 378-400. + */ +#if defined HAVE_ATTRIBUTE_TARGET_AVX512F_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS +#if !(defined(__clang__) && (__clang_major__ < 10 || (__clang_major__ == 10 && __clang_minor__ < 1))) +static npy_uint64 LOG_TABLE_TOP[64] = { + 0x0000000000000000, + 0x3F8FC0A8B1000000, + 0x3F9F829B0E780000, + 0x3FA77458F6340000, + 0x3FAF0A30C0100000, + 0x3FB341D7961C0000, + 0x3FB6F0D28AE60000, + 0x3FBA926D3A4A0000, + 0x3FBE27076E2A0000, + 0x3FC0D77E7CD10000, + 0x3FC29552F8200000, + 0x3FC44D2B6CCB0000, + 0x3FC5FF3070A80000, + 0x3FC7AB8902110000, + 0x3FC9525A9CF40000, + 0x3FCAF3C94E810000, + 0x3FCC8FF7C79B0000, + 0x3FCE27076E2B0000, + 0x3FCFB9186D5E0000, + 0x3FD0A324E2738000, + 0x3FD1675CABAB8000, + 0x3FD22941FBCF8000, + 0x3FD2E8E2BAE10000, + 0x3FD3A64C55698000, + 0x3FD4618BC21C8000, + 0x3FD51AAD872E0000, + 0x3FD5D1BDBF580000, + 0x3FD686C81E9B0000, + 0x3FD739D7F6BC0000, + 0x3FD7EAF83B828000, + 0x3FD89A3386C18000, + 0x3FD947941C210000, + 0x3FD9F323ECBF8000, + 0x3FDA9CEC9A9A0000, + 0x3FDB44F77BCC8000, + 0x3FDBEB4D9DA70000, + 0x3FDC8FF7C79A8000, + 0x3FDD32FE7E010000, + 0x3FDDD46A04C20000, + 0x3FDE744261D68000, + 0x3FDF128F5FAF0000, + 0x3FDFAF588F790000, + 0x3FE02552A5A5C000, + 0x3FE0723E5C1CC000, + 0x3FE0BE72E4254000, + 0x3FE109F39E2D4000, + 0x3FE154C3D2F4C000, + 0x3FE19EE6B467C000, + 0x3FE1E85F5E704000, + 0x3FE23130D7BEC000, + 0x3FE2795E1289C000, + 0x3FE2C0E9ED448000, + 0x3FE307D7334F0000, + 0x3FE34E289D9D0000, + 0x3FE393E0D3564000, + 0x3FE3D9026A714000, + 0x3FE41D8FE8468000, + 0x3FE4618BC21C4000, + 0x3FE4A4F85DB04000, + 0x3FE4E7D811B74000, + 0x3FE52A2D265BC000, + 0x3FE56BF9D5B40000, + 0x3FE5AD404C358000, + 0x3FE5EE02A9240000, +}; + +static npy_uint64 LOG_TABLE_TAIL[64] = { + 0x0000000000000000, + 0xBD5FE0E183092C59, + 0x3D2980267C7E09E4, + 0xBD62303B9CB0D5E1, + 0x3D662A6617CC9717, + 0xBD4717B6B33E44F8, + 0xBD62968C836CC8C2, + 0x3D6AAC6CA17A4554, + 0x3D6E5CBD3D50FFFC, + 0xBD6C69A65A23A170, + 0xBD35B967F4471DFC, + 0x3D6F4799F4F6543E, + 0xBD6B0B0DE3077D7E, + 0xBD537B720E4A694B, + 0x3D65AD1D904C1D4E, + 0xBD600349CC67F9B2, + 0xBD697794F689F843, + 0xBD3A342C2AF0003C, + 0x3D5F1546AAA3361C, + 0x3D50E35F73F7A018, + 0x3D630701CE63EAB9, + 0xBD3A6976F5EB0963, + 0x3D5D309C2CC91A85, + 0xBD6D0B1C68651946, + 0xBD609EC17A426426, + 0xBD3F4BD8DB0A7CC1, + 0x3D4394A11B1C1EE4, + 0x3D54AEC442BE1015, + 0xBD67FCB18ED9D603, + 0x3D67E1B259D2F3DA, + 0xBD6ED2A52C73BF78, + 0x3D56FABA4CDD147D, + 0x3D584BF2B68D766F, + 0x3D40931A909FEA5E, + 0x3D4EC5197DDB55D3, + 0x3D5B7BF7861D37AC, + 0x3D5A21AC25DB1EF3, + 0xBD542A9E21373414, + 0xBD6DAFA08CECADB1, + 0x3D3E1F8DF68DBCF3, + 0x3D3BB2CD720EC44C, + 0xBD49C24CA098362B, + 0x3D60FEC69C695D7F, + 0x3D6F404E57963891, + 0xBD657D49676844CC, + 0x3D592DFBC7D93617, + 0x3D65E9A98F33A396, + 0x3D52DD98B97BAEF0, + 0x3D1A07BD8B34BE7C, + 0xBD17AFA4392F1BA7, + 0xBD5DCA290F818480, + 0x3D5D1772F5386374, + 0x3D60BE1FB590A1F5, + 0xBD6E2CE9146D271A, + 0xBD65E6563BBD9FC9, + 0x3D66FAA404263D0B, + 0xBD5AA33736867A17, + 0x3D6EC27D0B7B37B3, + 0xBD244FDD840B8591, + 0x3D6BB09CB0985646, + 0x3D46ABB9DF22BC57, + 0xBD58CD7DC73BD194, + 0x3D6F2CFB29AAA5F0, + 0x3D66757006095FD2, +}; + +#define NPY_TANG_LOG_A1 0x1.55555555554e6p-4 +#define NPY_TANG_LOG_A2 0x1.9999999bac6d4p-7 +#define NPY_TANG_LOG_A3 0x1.2492307f1519fp-9 +#define NPY_TANG_LOG_A4 0x1.c8034c85dfffp-12 + +#define NPY_TANG_LOG_LN2HI 0x1.62e42fefa4p-1 +#define NPY_TANG_LOG_LN2LO -0x1.8432a1b0e2634p-43 +#endif +#endif + #endif diff --git a/numpy/core/src/umath/simd.inc.src b/numpy/core/src/umath/simd.inc.src index 48e89915c..7866f8143 100644 --- a/numpy/core/src/umath/simd.inc.src +++ b/numpy/core/src/umath/simd.inc.src @@ -114,16 +114,16 @@ nomemoverlap(char *ip, * should have no overlap in memory. */ #define IS_BINARY_SMALL_STEPS_AND_NOMEMOVERLAP \ - ((abs(steps[0]) < MAX_STEP_SIZE) && \ - (abs(steps[1]) < MAX_STEP_SIZE) && \ - (abs(steps[2]) < MAX_STEP_SIZE) && \ + ((labs(steps[0]) < MAX_STEP_SIZE) && \ + (labs(steps[1]) < MAX_STEP_SIZE) && \ + (labs(steps[2]) < MAX_STEP_SIZE) && \ (nomemoverlap(args[0], steps[0] * dimensions[0], args[2], steps[2] * dimensions[0])) && \ (nomemoverlap(args[1], steps[1] * dimensions[0], args[2], steps[2] * dimensions[0]))) #define IS_UNARY_TWO_OUT_SMALL_STEPS_AND_NOMEMOVERLAP \ - ((abs(steps[0]) < MAX_STEP_SIZE) && \ - (abs(steps[1]) < MAX_STEP_SIZE) && \ - (abs(steps[2]) < MAX_STEP_SIZE) && \ + ((labs(steps[0]) < MAX_STEP_SIZE) && \ + (labs(steps[1]) < MAX_STEP_SIZE) && \ + (labs(steps[2]) < MAX_STEP_SIZE) && \ (nomemoverlap(args[0], steps[0] * dimensions[0], args[2], steps[2] * dimensions[0])) && \ (nomemoverlap(args[0], steps[0] * dimensions[0], args[1], steps[1] * dimensions[0]))) @@ -132,8 +132,9 @@ nomemoverlap(char *ip, * 2) Input step should be smaller than MAX_STEP_SIZE for performance * 3) Input and output arrays should have no overlap in memory */ -#define IS_OUTPUT_BLOCKABLE_UNARY(esize, vsize) \ - (steps[1] == (esize) && abs(steps[0]) < MAX_STEP_SIZE && \ +#define IS_OUTPUT_BLOCKABLE_UNARY(esizein, esizeout, vsize) \ + ((steps[0] & (esizein-1)) == 0 && \ + steps[1] == (esizeout) && labs(steps[0]) < MAX_STEP_SIZE && \ (nomemoverlap(args[1], steps[1] * dimensions[0], args[0], steps[0] * dimensions[0]))) #define IS_BLOCKABLE_REDUCE(esize, vsize) \ @@ -251,7 +252,7 @@ static NPY_INLINE int run_unary_avx512f_@func@_@TYPE@(char **args, const npy_intp *dimensions, const npy_intp *steps) { #if defined HAVE_ATTRIBUTE_TARGET_AVX512F_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS - if ((IS_OUTPUT_BLOCKABLE_UNARY((npy_uint)(@esize@/@outsize@), 64)) && (labs(steps[0]) < 2*@max_stride@*@esize@)) { + if ((IS_OUTPUT_BLOCKABLE_UNARY(@esize@, (npy_uint)(@esize@/@outsize@), 64)) && (labs(steps[0]) < 2*@max_stride@*@esize@)) { AVX512F_@func@_@TYPE@((@type@*)args[1], (@type@*)args[0], dimensions[0], steps[0]); return 1; } @@ -358,7 +359,7 @@ static NPY_INLINE int run_@func@_avx512_skx_@TYPE@(char **args, npy_intp const *dimensions, npy_intp const *steps) { #if defined HAVE_ATTRIBUTE_TARGET_AVX512_SKX_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS && @EXISTS@ - if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(npy_bool), 64)) { + if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(@type@), sizeof(npy_bool), 64)) { AVX512_SKX_@func@_@TYPE@((npy_bool*)args[1], (@type@*)args[0], dimensions[0], steps[0]); return 1; } @@ -400,7 +401,7 @@ static NPY_INLINE int run_unary_@isa@_@func@_@TYPE@(char **args, npy_intp const *dimensions, npy_intp const *steps) { #if defined @CHK@ && defined NPY_HAVE_SSE2_INTRINSICS - if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(@type@), @REGISTER_SIZE@)) { + if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(@type@), sizeof(@type@), @REGISTER_SIZE@)) { @ISA@_@func@_@TYPE@((@type@*)args[1], (@type@*)args[0], dimensions[0], steps[0]); return 1; } @@ -426,7 +427,7 @@ static NPY_INLINE int run_unary_@isa@_@func@_FLOAT(char **args, npy_intp const *dimensions, npy_intp const *steps) { #if defined @CHK@ && defined NPY_HAVE_SSE2_INTRINSICS - if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(npy_float), @REGISTER_SIZE@)) { + if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(npy_float), sizeof(npy_float), @REGISTER_SIZE@)) { @ISA@_@func@_FLOAT((npy_float*)args[1], (npy_float*)args[0], dimensions[0], steps[0]); return 1; } @@ -447,7 +448,7 @@ static NPY_INLINE int run_unary_@isa@_sincos_FLOAT(char **args, npy_intp const *dimensions, npy_intp const *steps, NPY_TRIG_OP my_trig_op) { #if defined @CHK@ && defined NPY_HAVE_SSE2_INTRINSICS - if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(npy_float), @REGISTER_SIZE@)) { + if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(npy_float), sizeof(npy_float), @REGISTER_SIZE@)) { @ISA@_sincos_FLOAT((npy_float*)args[1], (npy_float*)args[0], dimensions[0], steps[0], my_trig_op); return 1; } @@ -468,7 +469,7 @@ run_unary_avx512f_exp_DOUBLE(char **args, npy_intp const *dimensions, npy_intp c { #if defined HAVE_ATTRIBUTE_TARGET_AVX512F_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS #if !(defined(__clang__) && (__clang_major__ < 10 || (__clang_major__ == 10 && __clang_minor__ < 1))) - if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(npy_double), 64)) { + if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(npy_double), sizeof(npy_double), 64)) { AVX512F_exp_DOUBLE((npy_double*)args[1], (npy_double*)args[0], dimensions[0], steps[0]); return 1; } @@ -479,6 +480,26 @@ run_unary_avx512f_exp_DOUBLE(char **args, npy_intp const *dimensions, npy_intp c return 0; } +#if defined HAVE_ATTRIBUTE_TARGET_AVX512F_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS +static NPY_INLINE void +AVX512F_log_DOUBLE(npy_double *, npy_double *, const npy_intp n, const npy_intp stride); +#endif +static NPY_INLINE int +run_unary_avx512f_log_DOUBLE(char **args, npy_intp const *dimensions, npy_intp const *steps) +{ +#if defined HAVE_ATTRIBUTE_TARGET_AVX512F_WITH_INTRINSICS && defined NPY_HAVE_SSE2_INTRINSICS +#if !(defined(__clang__) && (__clang_major__ < 10 || (__clang_major__ == 10 && __clang_minor__ < 1))) + if (IS_OUTPUT_BLOCKABLE_UNARY(sizeof(npy_double), sizeof(npy_double), 64)) { + AVX512F_log_DOUBLE((npy_double*)args[1], (npy_double*)args[0], dimensions[0], steps[0]); + return 1; + } + else + return 0; +#endif +#endif + return 0; +} + /**begin repeat * Float types * #type = npy_float, npy_double, npy_longdouble# @@ -1794,14 +1815,27 @@ avx512_permute_x4var_pd(__m512d t0, __m512d t3, __m512i index) { - - __mmask8 lut_mask = _mm512_cmp_epi64_mask(index, _mm512_set1_epi64(15), - _MM_CMPINT_GT); + __mmask8 lut_mask = _mm512_cmp_epi64_mask( + _mm512_and_epi64(_mm512_set1_epi64(0x10ULL), index), + _mm512_set1_epi64(0), _MM_CMPINT_GT); __m512d res1 = _mm512_permutex2var_pd(t0, index, t1); __m512d res2 = _mm512_permutex2var_pd(t2, index, t3); return _mm512_mask_blend_pd(lut_mask, res1, res2); } +static NPY_INLINE NPY_GCC_OPT_3 NPY_GCC_TARGET_AVX512F __m512d +avx512_permute_x8var_pd(__m512d t0, __m512d t1, __m512d t2, __m512d t3, + __m512d t4, __m512d t5, __m512d t6, __m512d t7, + __m512i index) +{ + __mmask8 lut_mask = _mm512_cmp_epi64_mask( + _mm512_and_epi64(_mm512_set1_epi64(0x20ULL), index), + _mm512_set1_epi64(0), _MM_CMPINT_GT); + __m512d res1 = avx512_permute_x4var_pd(t0, t1, t2, t3, index); + __m512d res2 = avx512_permute_x4var_pd(t4, t5, t6, t7, index); + return _mm512_mask_blend_pd(lut_mask, res1, res2); +} + /**begin repeat * #vsub = ps, pd# * #type= npy_float, npy_double# @@ -2698,17 +2732,17 @@ static NPY_GCC_OPT_3 NPY_GCC_TARGET_@ISA@ void /* process elements using glibc for large elements */ if (my_trig_op == npy_compute_cos) { - for (int ii = 0; iglibc_mask != 0; ii++) { + for (int ii = 0, jj = 0; iglibc_mask != 0; ii++, jj += stride) { if (iglibc_mask & 0x01) { - op[ii] = npy_cosf(ip[ii]); + op[ii] = npy_cosf(ip[jj]); } iglibc_mask = iglibc_mask >> 1; } } else { - for (int ii = 0; iglibc_mask != 0; ii++) { + for (int ii = 0, jj = 0; iglibc_mask != 0; ii++, jj += stride) { if (iglibc_mask & 0x01) { - op[ii] = npy_sinf(ip[ii]); + op[ii] = npy_sinf(ip[jj]); } iglibc_mask = iglibc_mask >> 1; } @@ -3144,6 +3178,206 @@ AVX512F_exp_DOUBLE(npy_double * op, #endif #endif +/* + * Vectorized implementation of log double using AVX512 + * Reference: + * [1] Tang, Ping Tak Peter. Table-lookup algorithms for elementary functions + * and their error analysis. No. CONF-9106103-1. Argonne National Lab., + * IL (USA), 1991. + * [2] Tang, Ping-Tak Peter. "Table-driven implementation of the logarithm + * function in IEEE floating-point arithmetic." ACM Transactions on + * Mathematical Software (TOMS) 16.4 (1990): 378-400. + * [3] Muller, Jean-Michel. "Elementary functions: algorithms and + * implementation." (2016). + * 1) if x = 0; return -INF + * 2) if x < 0; return NAN + * 3) if x is INF; return INF + * 4) if x is NAN; return NAN + * 5) if x on (1.0 - 0x1p-4, 1.0 + 0x1.09p-4), calling npy_log() + * 6) Range reduction: + * log(x) = log(2^m * z) + * = mln2 + log(z) + * 7) log(z) = log(z / c_k) + log(c_k); + * where c_k = 1 + k/64, k = 0,1,...,64 + * s.t. |x - c_k| <= 1/128 when x on[1,2]. + * 8) r = 2(x - c_k)/(x + c_k) + * log(x/c_k) = log((1 + r/2) / (1 - r/2)) + * = p(r) + * = 2((r/2) + 1/3*(r/2)^3 + 1/5*(r/2)^5 + ...) + */ +#if defined HAVE_ATTRIBUTE_TARGET_AVX512F_WITH_INTRINSICS +#if !(defined(__clang__) && (__clang_major__ < 10 || (__clang_major__ == 10 && __clang_minor__ < 1))) +static NPY_GCC_OPT_3 NPY_GCC_TARGET_AVX512F void +AVX512F_log_DOUBLE(npy_double * op, + npy_double * ip, + const npy_intp array_size, + const npy_intp steps) +{ + npy_intp num_remaining_elements = array_size; + const npy_intp stride = steps / (npy_intp)sizeof(npy_double); + const npy_int num_lanes = 64 / (npy_intp)sizeof(npy_double); + npy_int32 indexarr[8]; + for (npy_int32 ii = 0; ii < 8; ii++) { + indexarr[ii] = ii*stride; + } + + __m512d zeros_d = _mm512_set1_pd(0.0f); + __m512d ones_d = _mm512_set1_pd(1.0f); + __m512d mInf = _mm512_set1_pd(NPY_INFINITY); + __m512d mInv64 = (__m512d)(_mm512_set1_epi64(0x3f90000000000000)); + __m512d mNeg_nan = _mm512_set1_pd(-NPY_NAN); + __m512d mNan = _mm512_set1_pd(NPY_NAN); + __m512d mNeg_inf = _mm512_set1_pd(-NPY_INFINITY); + __m512d mA1 = _mm512_set1_pd(NPY_TANG_LOG_A1); + __m512d mA2 = _mm512_set1_pd(NPY_TANG_LOG_A2); + __m512d mA3 = _mm512_set1_pd(NPY_TANG_LOG_A3); + __m512d mA4 = _mm512_set1_pd(NPY_TANG_LOG_A4); + __m512d mLN2HI = _mm512_set1_pd(NPY_TANG_LOG_LN2HI); + __m512d mLN2LO = _mm512_set1_pd(NPY_TANG_LOG_LN2LO); + + __m512d mTo_glibc_min = _mm512_set1_pd(1.0 - 0x1p-4); + __m512d mTo_glibc_max = _mm512_set1_pd(1.0 + 0x1.09p-4); + __m256i vindex = _mm256_loadu_si256((__m256i*)&indexarr[0]); + + /* Load lookup table data */ + /**begin repeat + * #i = 0, 1, 2, 3, 4, 5, 6, 7# + */ + + __m512d mLUT_TOP_@i@ = _mm512_loadu_pd(&(LOG_TABLE_TOP[8*@i@])); + __m512d mLUT_TAIL_@i@ = _mm512_loadu_pd(&(LOG_TABLE_TAIL[8*@i@])); + + /**end repeat**/ + + __mmask8 load_mask = avx512_get_full_load_mask_pd(); + __mmask8 invalid_mask = avx512_get_partial_load_mask_pd(0, num_lanes); + __mmask8 divide_by_zero_mask = invalid_mask; + + __mmask8 inf_mask, nan_mask, zero_mask, negx_mask, denormal_mask, + glibc_mask; + + __m512d x; + while (num_remaining_elements > 0) { + if (num_remaining_elements < num_lanes) { + load_mask = avx512_get_partial_load_mask_pd(num_remaining_elements, + num_lanes); + } + + if (1 == stride) { + x = avx512_masked_load_pd(load_mask, ip); + } + else { + x = avx512_masked_gather_pd(zeros_d, ip, vindex, load_mask); + } + + /* call glibc when x on [1.0 - 0x1p-4, 1.0 + 0x1.09p-4] */ + __mmask8 m1 = _mm512_cmp_pd_mask(x, mTo_glibc_max, _CMP_LT_OQ); + __mmask8 m2 = _mm512_cmp_pd_mask(x, mTo_glibc_min, _CMP_GT_OQ); + glibc_mask = m1 & m2; + + if (glibc_mask != 0xFF) { + zero_mask = _mm512_cmp_pd_mask(x, zeros_d, _CMP_EQ_OQ); + inf_mask = _mm512_cmp_pd_mask(x, mInf, _CMP_EQ_OQ); + negx_mask = _mm512_cmp_pd_mask(x, zeros_d, _CMP_LT_OQ); + nan_mask = _mm512_cmp_pd_mask(x, x, _CMP_NEQ_UQ); + + divide_by_zero_mask = divide_by_zero_mask | (zero_mask & load_mask); + invalid_mask = invalid_mask | negx_mask; + + x = avx512_set_masked_lanes_pd(x, zeros_d, negx_mask); + __m512i ix = (__m512i)x; + + /* Normalize x when it is denormal */ + __m512i top12 = _mm512_and_epi64(ix, + _mm512_set1_epi64(0xfff0000000000000)); + denormal_mask = _mm512_cmp_epi64_mask(top12, _mm512_set1_epi64(0), + _CMP_EQ_OQ); + denormal_mask = (~zero_mask) & denormal_mask; + ix = (__m512i)_mm512_mask_mul_pd(x, denormal_mask, + x, _mm512_set1_pd(0x1p52)); + ix = _mm512_mask_sub_epi64(ix, denormal_mask, + ix, _mm512_set1_epi64(52ULL << 52)); + + /* + * x = 2^k * z; where z in range [1,2] + */ + __m512i tmp = _mm512_sub_epi64(ix, + _mm512_set1_epi64(0x3ff0000000000000)); + __m512i i = _mm512_and_epi64(_mm512_srai_epi64(tmp, 52 - 6), + _mm512_set1_epi64(0x3fULL)); + __m512i ik = _mm512_srai_epi64(tmp, 52); + __m512d z = (__m512d)(_mm512_sub_epi64(ix, _mm512_and_epi64(tmp, + _mm512_set1_epi64(0xfff0000000000000)))); + /* c = i/64 + 1 */ + __m256i i_32 = _mm512_cvtepi64_epi32(i); + __m512d c = _mm512_fmadd_pd(_mm512_cvtepi32_pd(i_32), mInv64, ones_d); + + /* u = 2 * (z - c) / (z + c) */ + __m512d u = _mm512_div_pd(_mm512_sub_pd(z, c), _mm512_add_pd(z, c)); + u = _mm512_mul_pd(_mm512_set1_pd(2.0), u); + + /* v = u * u */ + __m512d v = _mm512_mul_pd(u,u); + + /* log(z/c) = u + u*v*(A1 + v*(A2 + v*(A3 + v*A4))) */ + __m512d res = _mm512_fmadd_pd(v, mA4, mA3); + res = _mm512_fmadd_pd(v, res, mA2); + res = _mm512_fmadd_pd(v, res, mA1); + res = _mm512_mul_pd(v, res); + res = _mm512_fmadd_pd(u, res, u); + + /* Load lookup table data */ + __m512d c_hi = avx512_permute_x8var_pd(mLUT_TOP_0, mLUT_TOP_1, + mLUT_TOP_2, mLUT_TOP_3, mLUT_TOP_4, mLUT_TOP_5, + mLUT_TOP_6, mLUT_TOP_7, i); + __m512d c_lo = avx512_permute_x8var_pd(mLUT_TAIL_0, mLUT_TAIL_1, + mLUT_TAIL_2, mLUT_TAIL_3, mLUT_TAIL_4, mLUT_TAIL_5, + mLUT_TAIL_6, mLUT_TAIL_7, i); + + /* + * log(x) = k * ln2_hi + c_hi + + * k * ln2_lo + c_lo + + * log(z/c) + */ + __m256i ik_32 = _mm512_cvtepi64_epi32(ik); + __m512d k = _mm512_cvtepi32_pd(ik_32); + __m512d tt = _mm512_fmadd_pd(k, mLN2HI, c_hi); + __m512d tt2 = _mm512_fmadd_pd(k, mLN2LO, c_lo); + tt = _mm512_add_pd(tt, tt2); + res = _mm512_add_pd(tt, res); + + /* return special cases */ + res = avx512_set_masked_lanes_pd(res, mNan, nan_mask); + res = avx512_set_masked_lanes_pd(res, mNeg_nan, negx_mask); + res = avx512_set_masked_lanes_pd(res, mNeg_inf, zero_mask); + res = avx512_set_masked_lanes_pd(res, mInf, inf_mask); + + _mm512_mask_storeu_pd(op, load_mask, res); + } + + /* call glibc's log func when x around 1.0f */ + for (int ii = 0, jj = 0; glibc_mask != 0; ii++, jj += stride) { + if (glibc_mask & 0x01) { + op[ii] = npy_log(ip[jj]); + } + glibc_mask = glibc_mask >> 1; + } + + ip += num_lanes * stride; + op += num_lanes; + num_remaining_elements -= num_lanes; + } + + if (invalid_mask) { + npy_set_floatstatus_invalid(); + } + if (divide_by_zero_mask) { + npy_set_floatstatus_divbyzero(); + } +} +#endif +#endif + /**begin repeat * #TYPE = CFLOAT, CDOUBLE# * #type = npy_float, npy_double# diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index b35f377d7..8f841c6fa 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -5388,13 +5388,11 @@ ufunc_traverse(PyUFuncObject *self, visitproc visit, void *arg) static PyObject * ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds) { - int i; int errval; PyObject *override = NULL; PyObject *ret; PyArrayObject *ap1 = NULL, *ap2 = NULL, *ap_new = NULL; PyObject *new_args, *tmp; - PyObject *shape1, *shape2, *newshape; static PyObject *_numpy_matrix; @@ -5435,7 +5433,19 @@ ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds) "matrix", &_numpy_matrix); + const char *matrix_deprecation_msg = ( + "%s.outer() was passed a numpy matrix as %s argument. " + "Special handling of matrix is deprecated and will result in an " + "error in most cases. Please convert the matrix to a NumPy " + "array to retain the old behaviour. You can use `matrix.A` " + "to achieve this."); + if (PyObject_IsInstance(tmp, _numpy_matrix)) { + /* DEPRECATED 2020-05-13, NumPy 1.20 */ + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + matrix_deprecation_msg, ufunc->name, "first") < 0) { + return NULL; + } ap1 = (PyArrayObject *) PyArray_FromObject(tmp, NPY_NOTYPE, 0, 0); } else { @@ -5450,6 +5460,11 @@ ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds) return NULL; } if (PyObject_IsInstance(tmp, _numpy_matrix)) { + /* DEPRECATED 2020-05-13, NumPy 1.20 */ + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, + matrix_deprecation_msg, ufunc->name, "second") < 0) { + return NULL; + } ap2 = (PyArrayObject *) PyArray_FromObject(tmp, NPY_NOTYPE, 0, 0); } else { @@ -5460,34 +5475,45 @@ ufunc_outer(PyUFuncObject *ufunc, PyObject *args, PyObject *kwds) Py_DECREF(ap1); return NULL; } - /* Construct new shape tuple */ - shape1 = PyTuple_New(PyArray_NDIM(ap1)); - if (shape1 == NULL) { - goto fail; - } - for (i = 0; i < PyArray_NDIM(ap1); i++) { - PyTuple_SET_ITEM(shape1, i, - PyLong_FromLongLong((npy_longlong)PyArray_DIMS(ap1)[i])); - } - shape2 = PyTuple_New(PyArray_NDIM(ap2)); - for (i = 0; i < PyArray_NDIM(ap2); i++) { - PyTuple_SET_ITEM(shape2, i, PyInt_FromLong((long) 1)); + /* Construct new shape from ap1 and ap2 and then reshape */ + PyArray_Dims newdims; + npy_intp newshape[NPY_MAXDIMS]; + newdims.len = PyArray_NDIM(ap1) + PyArray_NDIM(ap2); + newdims.ptr = newshape; + + if (newdims.len > NPY_MAXDIMS) { + PyErr_Format(PyExc_ValueError, + "maximum supported dimension for an ndarray is %d, but " + "`%s.outer()` result would have %d.", + NPY_MAXDIMS, ufunc->name, newdims.len); + return NPY_FAIL; } - if (shape2 == NULL) { - Py_DECREF(shape1); + if (newdims.ptr == NULL) { goto fail; } - newshape = PyNumber_Add(shape1, shape2); - Py_DECREF(shape1); - Py_DECREF(shape2); - if (newshape == NULL) { - goto fail; + memcpy(newshape, PyArray_DIMS(ap1), PyArray_NDIM(ap1) * sizeof(npy_intp)); + for (int i = PyArray_NDIM(ap1); i < newdims.len; i++) { + newshape[i] = 1; } - ap_new = (PyArrayObject *)PyArray_Reshape(ap1, newshape); - Py_DECREF(newshape); + + ap_new = (PyArrayObject *)PyArray_Newshape(ap1, &newdims, NPY_CORDER); if (ap_new == NULL) { goto fail; } + if (PyArray_NDIM(ap_new) != newdims.len || + !PyArray_CompareLists(PyArray_DIMS(ap_new), newshape, newdims.len)) { + PyErr_Format(PyExc_TypeError, + "%s.outer() called with ndarray-subclass of type '%s' " + "which modified its shape after a reshape. `outer()` relies " + "on reshaping the inputs and is for example not supported for " + "the 'np.matrix' class (the usage of matrix is generally " + "discouraged). " + "To work around this issue, please convert the inputs to " + "numpy arrays.", + ufunc->name, Py_TYPE(ap_new)->tp_name); + goto fail; + } + new_args = Py_BuildValue("(OO)", ap_new, ap2); Py_DECREF(ap1); Py_DECREF(ap2); diff --git a/numpy/core/tests/data/umath-validation-set-log b/numpy/core/tests/data/umath-validation-set-log index a7bd98481..b8f6b0875 100644 --- a/numpy/core/tests/data/umath-validation-set-log +++ b/numpy/core/tests/data/umath-validation-set-log @@ -116,3 +116,156 @@ np.float32,0x3f494ab1,0xbe763131,4 np.float32,0x3f476b69,0xbe7fc2c6,4 np.float32,0x3f4884e8,0xbe7a214a,4 np.float32,0x3f486945,0xbe7aae76,4 +#float64 +## +ve denormal ## +np.float64,0x0000000000000001,0xc0874385446d71c3,1 +np.float64,0x0001000000000000,0xc086395a2079b70c,1 +np.float64,0x000fffffffffffff,0xc086232bdd7abcd2,1 +np.float64,0x0007ad63e2168cb6,0xc086290bc0b2980f,1 +## -ve denormal ## +np.float64,0x8000000000000001,0xfff8000000000001,1 +np.float64,0x8001000000000000,0xfff8000000000001,1 +np.float64,0x800fffffffffffff,0xfff8000000000001,1 +np.float64,0x8007ad63e2168cb6,0xfff8000000000001,1 +## +/-0.0f, MAX, MIN## +np.float64,0x0000000000000000,0xfff0000000000000,1 +np.float64,0x8000000000000000,0xfff0000000000000,1 +np.float64,0x7fefffffffffffff,0x40862e42fefa39ef,1 +np.float64,0xffefffffffffffff,0xfff8000000000001,1 +## near 1.0f ## +np.float64,0x3ff0000000000000,0x0000000000000000,1 +np.float64,0x3fe8000000000000,0xbfd269621134db92,1 +np.float64,0x3ff0000000000001,0x3cafffffffffffff,1 +np.float64,0x3ff0000020000000,0x3e7fffffe000002b,1 +np.float64,0x3ff0000000000001,0x3cafffffffffffff,1 +np.float64,0x3fefffffe0000000,0xbe70000008000005,1 +np.float64,0x3fefffffffffffff,0xbca0000000000000,1 +## random numbers ## +np.float64,0x02500186f3d9da56,0xc0855b8abf135773,1 +np.float64,0x09200815a3951173,0xc082ff1ad7131bdc,1 +np.float64,0x0da029623b0243d4,0xc0816fc994695bb5,1 +np.float64,0x48703b8ac483a382,0x40579213a313490b,1 +np.float64,0x09207b74c87c9860,0xc082fee20ff349ef,1 +np.float64,0x62c077698e8df947,0x407821c996d110f0,1 +np.float64,0x2350b45e87c3cfb0,0xc073d6b16b51d072,1 +np.float64,0x3990a23f9ff2b623,0xc051aa60eadd8c61,1 +np.float64,0x0d011386a116c348,0xc081a6cc7ea3b8fb,1 +np.float64,0x1fe0f0303ebe273a,0xc0763870b78a81ca,1 +np.float64,0x0cd1260121d387da,0xc081b7668d61a9d1,1 +np.float64,0x1e6135a8f581d422,0xc077425ac10f08c2,1 +np.float64,0x622168db5fe52d30,0x4077b3c669b9fadb,1 +np.float64,0x69f188e1ec6d1718,0x407d1e2f18c63889,1 +np.float64,0x3aa1bf1d9c4dd1a3,0xc04d682e24bde479,1 +np.float64,0x6c81c4011ce4f683,0x407ee5190e8a8e6a,1 +np.float64,0x2191fa55aa5a5095,0xc0750c0c318b5e2d,1 +np.float64,0x32a1f602a32bf360,0xc06270caa493fc17,1 +np.float64,0x16023c90ba93249b,0xc07d0f88e0801638,1 +np.float64,0x1c525fe6d71fa9ff,0xc078af49c66a5d63,1 +np.float64,0x1a927675815d65b7,0xc079e5bdd7fe376e,1 +np.float64,0x41227b8fe70da028,0x402aa0c9f9a84c71,1 +np.float64,0x4962bb6e853fe87d,0x405a34aa04c83747,1 +np.float64,0x23d2cda00b26b5a4,0xc0737c13a06d00ea,1 +np.float64,0x2d13083fd62987fa,0xc06a25055aeb474e,1 +np.float64,0x10e31e4c9b4579a1,0xc0804e181929418e,1 +np.float64,0x26d3247d556a86a9,0xc0716774171da7e8,1 +np.float64,0x6603379398d0d4ac,0x407a64f51f8a887b,1 +np.float64,0x02d38af17d9442ba,0xc0852d955ac9dd68,1 +np.float64,0x6a2382b4818dd967,0x407d4129d688e5d4,1 +np.float64,0x2ee3c403c79b3934,0xc067a091fefaf8b6,1 +np.float64,0x6493a699acdbf1a4,0x4079663c8602bfc5,1 +np.float64,0x1c8413c4f0de3100,0xc0788c99697059b6,1 +np.float64,0x4573f1ed350d9622,0x404e9bd1e4c08920,1 +np.float64,0x2f34265c9200b69c,0xc067310cfea4e986,1 +np.float64,0x19b43e65fa22029b,0xc07a7f8877de22d6,1 +np.float64,0x0af48ab7925ed6bc,0xc0825c4fbc0e5ade,1 +np.float64,0x4fa49699cad82542,0x4065c76d2a318235,1 +np.float64,0x7204a15e56ade492,0x40815bb87484dffb,1 +np.float64,0x4734aa08a230982d,0x40542a4bf7a361a9,1 +np.float64,0x1ae4ed296c2fd749,0xc079ac4921f20abb,1 +np.float64,0x472514ea4370289c,0x4053ff372bd8f18f,1 +np.float64,0x53a54b3f73820430,0x406b5411fc5f2e33,1 +np.float64,0x64754de5a15684fa,0x407951592e99a5ab,1 +np.float64,0x69358e279868a7c3,0x407c9c671a882c31,1 +np.float64,0x284579ec61215945,0xc0706688e55f0927,1 +np.float64,0x68b5c58806447adc,0x407c43d6f4eff760,1 +np.float64,0x1945a83f98b0e65d,0xc07acc15eeb032cc,1 +np.float64,0x0fc5eb98a16578bf,0xc080b0d02eddca0e,1 +np.float64,0x6a75e208f5784250,0x407d7a7383bf8f05,1 +np.float64,0x0fe63a029c47645d,0xc080a59ca1e98866,1 +np.float64,0x37963ac53f065510,0xc057236281f7bdb6,1 +np.float64,0x135661bb07067ff7,0xc07ee924930c21e4,1 +np.float64,0x4b4699469d458422,0x405f73843756e887,1 +np.float64,0x1a66d73e4bf4881b,0xc07a039ba1c63adf,1 +np.float64,0x12a6b9b119a7da59,0xc07f62e49c6431f3,1 +np.float64,0x24c719aa8fd1bdb5,0xc072d26da4bf84d3,1 +np.float64,0x0fa6ff524ffef314,0xc080bb8514662e77,1 +np.float64,0x1db751d66fdd4a9a,0xc077b77cb50d7c92,1 +np.float64,0x4947374c516da82c,0x4059e9acfc7105bf,1 +np.float64,0x1b1771ab98f3afc8,0xc07989326b8e1f66,1 +np.float64,0x25e78805baac8070,0xc0720a818e6ef080,1 +np.float64,0x4bd7a148225d3687,0x406082d004ea3ee7,1 +np.float64,0x53d7d6b2bbbda00a,0x406b9a398967cbd5,1 +np.float64,0x6997fb9f4e1c685f,0x407ce0a703413eba,1 +np.float64,0x069802c2ff71b951,0xc083df39bf7acddc,1 +np.float64,0x4d683ac9890f66d8,0x4062ae21d8c2acf0,1 +np.float64,0x5a2825863ec14f4c,0x40722d718d549552,1 +np.float64,0x0398799a88f4db80,0xc084e93dab8e2158,1 +np.float64,0x5ed87a8b77e135a5,0x40756d7051777b33,1 +np.float64,0x5828cd6d79b9bede,0x4070cafb22fc6ca1,1 +np.float64,0x7b18ba2a5ec6f068,0x408481386b3ed6fe,1 +np.float64,0x4938fd60922198fe,0x4059c206b762ea7e,1 +np.float64,0x31b8f44fcdd1a46e,0xc063b2faa8b6434e,1 +np.float64,0x5729341c0d918464,0x407019cac0c4a7d7,1 +np.float64,0x13595e9228ee878e,0xc07ee7235a7d8088,1 +np.float64,0x17698b0dc9dd4135,0xc07c1627e3a5ad5f,1 +np.float64,0x63b977c283abb0cc,0x4078cf1ec6ed65be,1 +np.float64,0x7349cc0d4dc16943,0x4081cc697ce4cb53,1 +np.float64,0x4e49a80b732fb28d,0x4063e67e3c5cbe90,1 +np.float64,0x07ba14b848a8ae02,0xc0837ac032a094e0,1 +np.float64,0x3da9f17b691bfddc,0xc03929c25366acda,1 +np.float64,0x02ea39aa6c3ac007,0xc08525af6f21e1c4,1 +np.float64,0x3a6a42f04ed9563d,0xc04e98e825dca46b,1 +np.float64,0x1afa877cd7900be7,0xc0799d6648cb34a9,1 +np.float64,0x58ea986649e052c6,0x4071512e939ad790,1 +np.float64,0x691abbc04647f536,0x407c89aaae0fcb83,1 +np.float64,0x43aabc5063e6f284,0x4044b45d18106fd2,1 +np.float64,0x488b003c893e0bea,0x4057df012a2dafbe,1 +np.float64,0x77eb076ed67caee5,0x40836720de94769e,1 +np.float64,0x5c1b46974aba46f4,0x40738731ba256007,1 +np.float64,0x1a5b29ecb5d3c261,0xc07a0becc77040d6,1 +np.float64,0x5d8b6ccf868c6032,0x4074865c1865e2db,1 +np.float64,0x4cfb6690b4aaf5af,0x406216cd8c7e8ddb,1 +np.float64,0x76cbd8eb5c5fc39e,0x4083038dc66d682b,1 +np.float64,0x28bbd1fec5012814,0xc07014c2dd1b9711,1 +np.float64,0x33dc1b3a4fd6bf7a,0xc060bd0756e07d8a,1 +np.float64,0x52bbe89b37de99f3,0x406a10041aa7d343,1 +np.float64,0x07bc479d15eb2dd3,0xc0837a1a6e3a3b61,1 +np.float64,0x18fc5275711a901d,0xc07aff3e9d62bc93,1 +np.float64,0x114c9758e247dc71,0xc080299a7cf15b05,1 +np.float64,0x25ac8f6d60755148,0xc07233c4c0c511d4,1 +np.float64,0x260cae2bb9e9fd7e,0xc071f128c7e82eac,1 +np.float64,0x572ccdfe0241de82,0x40701bedc84bb504,1 +np.float64,0x0ddcef6c8d41f5ee,0xc0815a7e16d07084,1 +np.float64,0x6dad1d59c988af68,0x407fb4a0bc0142b1,1 +np.float64,0x025d200580d8b6d1,0xc08556c0bc32b1b2,1 +np.float64,0x7aad344b6aa74c18,0x40845bbc453f22be,1 +np.float64,0x5b5d9d6ad9d14429,0x4073036d2d21f382,1 +np.float64,0x49cd8d8dcdf19954,0x405b5c034f5c7353,1 +np.float64,0x63edb9483335c1e6,0x4078f2dd21378786,1 +np.float64,0x7b1dd64c9d2c26bd,0x408482b922017bc9,1 +np.float64,0x782e13e0b574be5f,0x40837e2a0090a5ad,1 +np.float64,0x592dfe18b9d6db2f,0x40717f777fbcb1ec,1 +np.float64,0x654e3232ac60d72c,0x4079e71a95a70446,1 +np.float64,0x7b8e42ad22091456,0x4084a9a6f1e61722,1 +np.float64,0x570e88dfd5860ae6,0x407006ae6c0d137a,1 +np.float64,0x294e98346cb98ef1,0xc06f5edaac12bd44,1 +np.float64,0x1adeaa4ab792e642,0xc079b1431d5e2633,1 +np.float64,0x7b6ead3377529ac8,0x40849eabc8c7683c,1 +np.float64,0x2b8eedae8a9b2928,0xc06c400054deef11,1 +np.float64,0x65defb45b2dcf660,0x407a4b53f181c05a,1 +np.float64,0x1baf582d475e7701,0xc07920bcad4a502c,1 +np.float64,0x461f39cf05a0f15a,0x405126368f984fa1,1 +np.float64,0x7e5f6f5dcfff005b,0x4085a37d610439b4,1 +np.float64,0x136f66e4d09bd662,0xc07ed8a2719f2511,1 +np.float64,0x65afd8983fb6ca1f,0x407a2a7f48bf7fc1,1 +np.float64,0x572fa7f95ed22319,0x40701d706cf82e6f,1 diff --git a/numpy/core/tests/test_api.py b/numpy/core/tests/test_api.py index 2600d409a..067cadf78 100644 --- a/numpy/core/tests/test_api.py +++ b/numpy/core/tests/test_api.py @@ -291,7 +291,7 @@ def test_array_astype_warning(t): @pytest.mark.parametrize(["dtype", "out_dtype"], [(np.bytes_, np.bool_), - (np.unicode, np.bool_), + (np.unicode_, np.bool_), (np.dtype("S10,S9"), np.dtype("?,?"))]) def test_string_to_boolean_cast(dtype, out_dtype): """ @@ -305,7 +305,7 @@ def test_string_to_boolean_cast(dtype, out_dtype): @pytest.mark.parametrize(["dtype", "out_dtype"], [(np.bytes_, np.bool_), - (np.unicode, np.bool_), + (np.unicode_, np.bool_), (np.dtype("S10,S9"), np.dtype("?,?"))]) def test_string_to_boolean_cast_errors(dtype, out_dtype): """ @@ -542,3 +542,10 @@ def test_broadcast_arrays(): result = np.broadcast_arrays(a, b) assert_equal(result[0], np.array([(1, 2, 3), (1, 2, 3), (1, 2, 3)], dtype='u4,u4,u4')) assert_equal(result[1], np.array([(1, 2, 3), (4, 5, 6), (7, 8, 9)], dtype='u4,u4,u4')) + +@pytest.mark.parametrize(["shape", "fill_value", "expected_output"], + [((2, 2), [5.0, 6.0], np.array([[5.0, 6.0], [5.0, 6.0]])), + ((3, 2), [1.0, 2.0], np.array([[1.0, 2.0], [1.0, 2.0], [1.0, 2.0]]))]) +def test_full_from_list(shape, fill_value, expected_output): + output = np.full(shape, fill_value) + assert_equal(output, expected_output) diff --git a/numpy/core/tests/test_array_coercion.py b/numpy/core/tests/test_array_coercion.py new file mode 100644 index 000000000..30019b253 --- /dev/null +++ b/numpy/core/tests/test_array_coercion.py @@ -0,0 +1,572 @@ +""" +Tests for array coercion, mainly through testing `np.array` results directly. +Note that other such tests exist e.g. in `test_api.py` and many corner-cases +are tested (sometimes indirectly) elsewhere. +""" + +import pytest +from pytest import param + +from itertools import product + +import numpy as np +from numpy.core._rational_tests import rational + +from numpy.testing import ( + assert_array_equal, assert_warns, IS_PYPY) + + +def arraylikes(): + """ + Generator for functions converting an array into various array-likes. + If full is True (default) includes array-likes not capable of handling + all dtypes + """ + # base array: + def ndarray(a): + return a + + yield param(ndarray, id="ndarray") + + # subclass: + class MyArr(np.ndarray): + pass + + def subclass(a): + return a.view(MyArr) + + yield subclass + + # Array-interface + class ArrayDunder: + def __init__(self, a): + self.a = a + + def __array__(self, dtype=None): + return self.a + + yield param(ArrayDunder, id="__array__") + + # memory-view + yield param(memoryview, id="memoryview") + + # Array-interface + class ArrayInterface: + def __init__(self, a): + self.a = a # need to hold on to keep interface valid + self.__array_interface__ = a.__array_interface__ + + yield param(ArrayInterface, id="__array_interface__") + + # Array-Struct + class ArrayStruct: + def __init__(self, a): + self.a = a # need to hold on to keep struct valid + self.__array_struct__ = a.__array_struct__ + + yield param(ArrayStruct, id="__array_struct__") + + +def scalar_instances(times=True, extended_precision=True, user_dtype=True): + # Hard-coded list of scalar instances. + # Floats: + yield param(np.sqrt(np.float16(5)), id="float16") + yield param(np.sqrt(np.float32(5)), id="float32") + yield param(np.sqrt(np.float64(5)), id="float64") + if extended_precision: + yield param(np.sqrt(np.longdouble(5)), id="longdouble") + + # Complex: + yield param(np.sqrt(np.complex64(2+3j)), id="complex64") + yield param(np.sqrt(np.complex128(2+3j)), id="complex128") + if extended_precision: + yield param(np.sqrt(np.longcomplex(2+3j)), id="clongdouble") + + # Bool: + # XFAIL: Bool should be added, but has some bad properties when it + # comes to strings, see also gh-9875 + # yield param(np.bool_(0), id="bool") + + # Integers: + yield param(np.int8(2), id="int8") + yield param(np.int16(2), id="int16") + yield param(np.int32(2), id="int32") + yield param(np.int64(2), id="int64") + + yield param(np.uint8(2), id="uint8") + yield param(np.uint16(2), id="uint16") + yield param(np.uint32(2), id="uint32") + yield param(np.uint64(2), id="uint64") + + # Rational: + if user_dtype: + yield param(rational(1, 2), id="rational") + + # Cannot create a structured void scalar directly: + structured = np.array([(1, 3)], "i,i")[0] + assert isinstance(structured, np.void) + assert structured.dtype == np.dtype("i,i") + yield param(structured, id="structured") + + if times: + # Datetimes and timedelta + yield param(np.timedelta64(2), id="timedelta64[generic]") + yield param(np.timedelta64(23, "s"), id="timedelta64[s]") + yield param(np.timedelta64("NaT", "s"), id="timedelta64[s](NaT)") + + yield param(np.datetime64("NaT"), id="datetime64[generic](NaT)") + yield param(np.datetime64("2020-06-07 12:43", "ms"), id="datetime64[ms]") + + # Strings and unstructured void: + yield param(np.bytes_(b"1234"), id="bytes") + yield param(np.unicode_("2345"), id="unicode") + yield param(np.void(b"4321"), id="unstructured_void") + + +def is_parametric_dtype(dtype): + """Returns True if the the dtype is a parametric legacy dtype (itemsize + is 0, or a datetime without units) + """ + if dtype.itemsize == 0: + return True + if issubclass(dtype.type, (np.datetime64, np.timedelta64)): + if dtype.name.endswith("64"): + # Generic time units + return True + return False + + +class TestStringDiscovery: + @pytest.mark.parametrize("obj", + [object(), 1.2, 10**43, None, "string"], + ids=["object", "1.2", "10**43", "None", "string"]) + def test_basic_stringlength(self, obj): + length = len(str(obj)) + expected = np.dtype(f"S{length}") + + assert np.array(obj, dtype="S").dtype == expected + assert np.array([obj], dtype="S").dtype == expected + + # A nested array is also discovered correctly + arr = np.array(obj, dtype="O") + assert np.array(arr, dtype="S").dtype == expected + # Check that .astype() behaves identical + assert arr.astype("S").dtype == expected + + @pytest.mark.parametrize("obj", + [object(), 1.2, 10**43, None, "string"], + ids=["object", "1.2", "10**43", "None", "string"]) + def test_nested_arrays_stringlength(self, obj): + length = len(str(obj)) + expected = np.dtype(f"S{length}") + arr = np.array(obj, dtype="O") + assert np.array([arr, arr], dtype="S").dtype == expected + + @pytest.mark.parametrize("arraylike", arraylikes()) + def test_unpack_first_level(self, arraylike): + # We unpack exactly one level of array likes + obj = np.array([None]) + obj[0] = np.array(1.2) + # the length of the included item, not of the float dtype + length = len(str(obj[0])) + expected = np.dtype(f"S{length}") + + obj = arraylike(obj) + # casting to string usually calls str(obj) + arr = np.array([obj], dtype="S") + assert arr.shape == (1, 1) + assert arr.dtype == expected + + +class TestScalarDiscovery: + def test_void_special_case(self): + # Void dtypes with structures discover tuples as elements + arr = np.array((1, 2, 3), dtype="i,i,i") + assert arr.shape == () + arr = np.array([(1, 2, 3)], dtype="i,i,i") + assert arr.shape == (1,) + + def test_char_special_case(self): + arr = np.array("string", dtype="c") + assert arr.shape == (6,) + assert arr.dtype.char == "c" + arr = np.array(["string"], dtype="c") + assert arr.shape == (1, 6) + assert arr.dtype.char == "c" + + def test_char_special_case_deep(self): + # Check that the character special case errors correctly if the + # array is too deep: + nested = ["string"] # 2 dimensions (due to string being sequence) + for i in range(np.MAXDIMS - 2): + nested = [nested] + + arr = np.array(nested, dtype='c') + assert arr.shape == (1,) * (np.MAXDIMS - 1) + (6,) + with pytest.raises(ValueError): + np.array([nested], dtype="c") + + def test_unknown_object(self): + arr = np.array(object()) + assert arr.shape == () + assert arr.dtype == np.dtype("O") + + @pytest.mark.parametrize("scalar", scalar_instances()) + def test_scalar(self, scalar): + arr = np.array(scalar) + assert arr.shape == () + assert arr.dtype == scalar.dtype + + arr = np.array([[scalar, scalar]]) + assert arr.shape == (1, 2) + assert arr.dtype == scalar.dtype + + # Additionally to string this test also runs into a corner case + # with datetime promotion (the difference is the promotion order). + def test_scalar_promotion(self): + for sc1, sc2 in product(scalar_instances(), scalar_instances()): + sc1, sc2 = sc1.values[0], sc2.values[0] + # test all combinations: + try: + arr = np.array([sc1, sc2]) + except (TypeError, ValueError): + # The promotion between two times can fail + # XFAIL (ValueError): Some object casts are currently undefined + continue + assert arr.shape == (2,) + try: + dt1, dt2 = sc1.dtype, sc2.dtype + expected_dtype = np.promote_types(dt1, dt2) + assert arr.dtype == expected_dtype + except TypeError as e: + # Will currently always go to object dtype + assert arr.dtype == np.dtype("O") + + @pytest.mark.parametrize("scalar", scalar_instances()) + def test_scalar_coercion(self, scalar): + # This tests various scalar coercion paths, mainly for the numerical + # types. It includes some paths not directly related to `np.array` + if isinstance(scalar, np.inexact): + # Ensure we have a full-precision number if available + scalar = type(scalar)((scalar * 2)**0.5) + + if type(scalar) is rational: + # Rational generally fails due to a missing cast. In the future + # object casts should automatically be defined based on `setitem`. + pytest.xfail("Rational to object cast is undefined currently.") + + # Use casting from object: + arr = np.array(scalar, dtype=object).astype(scalar.dtype) + + # Test various ways to create an array containing this scalar: + arr1 = np.array(scalar).reshape(1) + arr2 = np.array([scalar]) + arr3 = np.empty(1, dtype=scalar.dtype) + arr3[0] = scalar + arr4 = np.empty(1, dtype=scalar.dtype) + arr4[:] = [scalar] + # All of these methods should yield the same results + assert_array_equal(arr, arr1) + assert_array_equal(arr, arr2) + assert_array_equal(arr, arr3) + assert_array_equal(arr, arr4) + + @pytest.mark.xfail(IS_PYPY, reason="`int(np.complex128(3))` fails on PyPy") + @pytest.mark.filterwarnings("ignore::numpy.ComplexWarning") + @pytest.mark.parametrize("cast_to", scalar_instances()) + def test_scalar_coercion_same_as_cast_and_assignment(self, cast_to): + """ + Test that in most cases: + * `np.array(scalar, dtype=dtype)` + * `np.empty((), dtype=dtype)[()] = scalar` + * `np.array(scalar).astype(dtype)` + should behave the same. The only exceptions are paramteric dtypes + (mainly datetime/timedelta without unit) and void without fields. + """ + dtype = cast_to.dtype # use to parametrize only the target dtype + + for scalar in scalar_instances(times=False): + scalar = scalar.values[0] + + if dtype.type == np.void: + if scalar.dtype.fields is not None and dtype.fields is None: + # Here, coercion to "V6" works, but the cast fails. + # Since the types are identical, SETITEM takes care of + # this, but has different rules than the cast. + with pytest.raises(TypeError): + np.array(scalar).astype(dtype) + np.array(scalar, dtype=dtype) + np.array([scalar], dtype=dtype) + continue + + # The main test, we first try to use casting and if it succeeds + # continue below testing that things are the same, otherwise + # test that the alternative paths at least also fail. + try: + cast = np.array(scalar).astype(dtype) + except (TypeError, ValueError, RuntimeError): + # coercion should also raise (error type may change) + with pytest.raises(Exception): + np.array(scalar, dtype=dtype) + # assignment should also raise + res = np.zeros((), dtype=dtype) + with pytest.raises(Exception): + res[()] = scalar + + return + + # Non error path: + arr = np.array(scalar, dtype=dtype) + assert_array_equal(arr, cast) + # assignment behaves the same + ass = np.zeros((), dtype=dtype) + ass[()] = scalar + assert_array_equal(ass, cast) + + +class TestTimeScalars: + @pytest.mark.parametrize("dtype", [np.int64, np.float32]) + @pytest.mark.parametrize("scalar", + [param(np.timedelta64("NaT", "s"), id="timedelta64[s](NaT)"), + param(np.timedelta64(123, "s"), id="timedelta64[s]"), + param(np.datetime64("NaT", "generic"), id="datetime64[generic](NaT)"), + param(np.datetime64(1, "D"), id="datetime64[D]")],) + def test_coercion_basic(self, dtype, scalar): + arr = np.array(scalar, dtype=dtype) + cast = np.array(scalar).astype(dtype) + ass = np.ones((), dtype=dtype) + ass[()] = scalar # raises, as would np.array([scalar], dtype=dtype) + + assert_array_equal(arr, cast) + assert_array_equal(cast, cast) + + @pytest.mark.parametrize("dtype", [np.int64, np.float32]) + @pytest.mark.parametrize("scalar", + [param(np.timedelta64(123, "ns"), id="timedelta64[ns]"), + param(np.timedelta64(12, "generic"), id="timedelta64[generic]")]) + def test_coercion_timedelta_convert_to_number(self, dtype, scalar): + # Only "ns" and "generic" timedeltas can be converted to numbers + # so these are slightly special. + arr = np.array(scalar, dtype=dtype) + cast = np.array(scalar).astype(dtype) + ass = np.ones((), dtype=dtype) + ass[()] = scalar # raises, as would np.array([scalar], dtype=dtype) + + assert_array_equal(arr, cast) + assert_array_equal(cast, cast) + + @pytest.mark.parametrize("dtype", ["S6", "U6"]) + @pytest.mark.parametrize(["val", "unit"], + [param(123, "s", id="[s]"), param(123, "D", id="[D]")]) + def test_coercion_assignment_datetime(self, val, unit, dtype): + # String from datetime64 assignment is currently special cased to + # never use casting. This is because casting will error in this + # case, and traditionally in most cases the behaviour is maintained + # like this. (`np.array(scalar, dtype="U6")` would have failed before) + # TODO: This discrepency _should_ be resolved, either by relaxing the + # cast, or by deprecating the first part. + scalar = np.datetime64(val, unit) + dtype = np.dtype(dtype) + cut_string = dtype.type(str(scalar)[:6]) + + arr = np.array(scalar, dtype=dtype) + assert arr[()] == cut_string + ass = np.ones((), dtype=dtype) + ass[()] = scalar + assert ass[()] == cut_string + + with pytest.raises(RuntimeError): + # However, unlike the above assignment using `str(scalar)[:6]` + # due to being handled by the string DType and not be casting + # the explicit cast fails: + np.array(scalar).astype(dtype) + + + @pytest.mark.parametrize(["val", "unit"], + [param(123, "s", id="[s]"), param(123, "D", id="[D]")]) + def test_coercion_assignment_timedelta(self, val, unit): + scalar = np.timedelta64(val, unit) + + # Unlike datetime64, timedelta allows the unsafe cast: + np.array(scalar, dtype="S6") + cast = np.array(scalar).astype("S6") + ass = np.ones((), dtype="S6") + ass[()] = scalar + expected = scalar.astype("S")[:6] + assert cast[()] == expected + assert ass[()] == expected + +class TestNested: + def test_nested_simple(self): + initial = [1.2] + nested = initial + for i in range(np.MAXDIMS - 1): + nested = [nested] + + arr = np.array(nested, dtype="float64") + assert arr.shape == (1,) * np.MAXDIMS + with pytest.raises(ValueError): + np.array([nested], dtype="float64") + + # We discover object automatically at this time: + with assert_warns(np.VisibleDeprecationWarning): + arr = np.array([nested]) + assert arr.dtype == np.dtype("O") + assert arr.shape == (1,) * np.MAXDIMS + assert arr.item() is initial + + def test_pathological_self_containing(self): + # Test that this also works for two nested sequences + l = [] + l.append(l) + arr = np.array([l, l, l], dtype=object) + assert arr.shape == (3,) + (1,) * (np.MAXDIMS - 1) + + # Also check a ragged case: + arr = np.array([l, [None], l], dtype=object) + assert arr.shape == (3, 1) + + @pytest.mark.parametrize("arraylike", arraylikes()) + def test_nested_arraylikes(self, arraylike): + # We try storing an array like into an array, but the array-like + # will have too many dimensions. This means the shape discovery + # decides that the array-like must be treated as an object (a special + # case of ragged discovery). The result will be an array with one + # dimension less than the maximum dimensions, and the array being + # assigned to it (which does work for object or if `float(arraylike)` + # works). + initial = arraylike(np.ones((1, 1))) + + nested = initial + for i in range(np.MAXDIMS - 1): + nested = [nested] + + with pytest.raises(ValueError): + # It will refuse to assign the array into + np.array(nested, dtype="float64") + + # If this is object, we end up assigning a (1, 1) array into (1,) + # (due to running out of dimensions), this is currently supported but + # a special case which is not ideal. + arr = np.array(nested, dtype=object) + assert arr.shape == (1,) * np.MAXDIMS + assert arr.item() == np.array(initial).item() + + @pytest.mark.parametrize("arraylike", arraylikes()) + def test_uneven_depth_ragged(self, arraylike): + arr = np.arange(4).reshape((2, 2)) + arr = arraylike(arr) + + # Array is ragged in the second dimension already: + out = np.array([arr, [arr]], dtype=object) + assert out.shape == (2,) + assert out[0] is arr + assert type(out[1]) is list + + # Array is ragged in the third dimension: + with pytest.raises(ValueError): + # This is a broadcast error during assignment, because + # the array shape would be (2, 2, 2) but `arr[0, 0] = arr` fails. + np.array([arr, [arr, arr]], dtype=object) + + def test_empty_sequence(self): + arr = np.array([[], [1], [[1]]], dtype=object) + assert arr.shape == (3,) + + # The empty sequence stops further dimension discovery, so the + # result shape will be (0,) which leads to an error during: + with pytest.raises(ValueError): + np.array([[], np.empty((0, 1))], dtype=object) + + +class TestBadSequences: + # These are tests for bad objects passed into `np.array`, in general + # these have undefined behaviour. In the old code they partially worked + # when now they will fail. We could (and maybe should) create a copy + # of all sequences to be safe against bad-actors. + + def test_growing_list(self): + # List to coerce, `mylist` will append to it during coercion + obj = [] + class mylist(list): + def __len__(self): + obj.append([1, 2]) + return super().__len__() + + obj.append(mylist([1, 2])) + + with pytest.raises(RuntimeError): + np.array(obj) + + # Note: We do not test a shrinking list. These do very evil things + # and the only way to fix them would be to copy all sequences. + # (which may be a real option in the future). + + def test_mutated_list(self): + # List to coerce, `mylist` will mutate the first element + obj = [] + class mylist(list): + def __len__(self): + obj[0] = [2, 3] # replace with a different list. + return super().__len__() + + obj.append([2, 3]) + obj.append(mylist([1, 2])) + with pytest.raises(RuntimeError): + np.array(obj) + + def test_replace_0d_array(self): + # List to coerce, `mylist` will mutate the first element + obj = [] + class baditem: + def __len__(self): + obj[0][0] = 2 # replace with a different list. + raise ValueError("not actually a sequence!") + + def __getitem__(self): + pass + + # Runs into a corner case in the new code, the `array(2)` is cached + # so replacing it invalidates the cache. + obj.append([np.array(2), baditem()]) + with pytest.raises(RuntimeError): + np.array(obj) + + +class TestArrayLikes: + @pytest.mark.parametrize("arraylike", arraylikes()) + def test_0d_object_special_case(self, arraylike): + arr = np.array(0.) + obj = arraylike(arr) + # A single array-like is always converted: + res = np.array(obj, dtype=object) + assert_array_equal(arr, res) + + # But a single 0-D nested array-like never: + res = np.array([obj], dtype=object) + assert res[0] is obj + + def test_0d_generic_special_case(self): + class ArraySubclass(np.ndarray): + def __float__(self): + raise TypeError("e.g. quantities raise on this") + + arr = np.array(0.) + obj = arr.view(ArraySubclass) + res = np.array(obj) + # The subclass is simply cast: + assert_array_equal(arr, res) + + # If the 0-D array-like is included, __float__ is currently + # guaranteed to be used. We may want to change that, quantities + # and masked arrays half make use of this. + with pytest.raises(TypeError): + np.array([obj]) + + # The same holds for memoryview: + obj = memoryview(arr) + res = np.array(obj) + assert_array_equal(arr, res) + with pytest.raises(ValueError): + # The error type does not matter much here. + np.array([obj]) diff --git a/numpy/core/tests/test_conversion_utils.py b/numpy/core/tests/test_conversion_utils.py index e96113d09..d8849ee29 100644 --- a/numpy/core/tests/test_conversion_utils.py +++ b/numpy/core/tests/test_conversion_utils.py @@ -7,19 +7,31 @@ import pytest import numpy as np import numpy.core._multiarray_tests as mt +from numpy.testing import assert_warns class StringConverterTestCase: allow_bytes = True case_insensitive = True exact_match = False + warn = True def _check_value_error(self, val): pattern = r'\(got {}\)'.format(re.escape(repr(val))) with pytest.raises(ValueError, match=pattern) as exc: self.conv(val) + def _check_conv_assert_warn(self, val, expected): + if self.warn: + with assert_warns(DeprecationWarning) as exc: + assert self.conv(val) == expected + else: + assert self.conv(val) == expected + def _check(self, val, expected): + """Takes valid non-deprecated inputs for converters, + runs converters on inputs, checks correctness of outputs, + warnings and errors""" assert self.conv(val) == expected if self.allow_bytes: @@ -33,13 +45,13 @@ class StringConverterTestCase: self._check_value_error(val[:1]) self._check_value_error(val + '\0') else: - assert self.conv(val[:1]) == expected + self._check_conv_assert_warn(val[:1], expected) if self.case_insensitive: if val != val.lower(): - assert self.conv(val.lower()) == expected + self._check_conv_assert_warn(val.lower(), expected) if val != val.upper(): - assert self.conv(val.upper()) == expected + self._check_conv_assert_warn(val.upper(), expected) else: if val != val.lower(): self._check_value_error(val.lower()) @@ -69,6 +81,8 @@ class StringConverterTestCase: class TestByteorderConverter(StringConverterTestCase): """ Tests of PyArray_ByteorderConverter """ conv = mt.run_byteorder_converter + warn = False + def test_valid(self): for s in ['big', '>']: self._check(s, 'NPY_BIG') @@ -85,10 +99,12 @@ class TestByteorderConverter(StringConverterTestCase): class TestSortkindConverter(StringConverterTestCase): """ Tests of PyArray_SortkindConverter """ conv = mt.run_sortkind_converter + warn = False + def test_valid(self): - self._check('quick', 'NPY_QUICKSORT') - self._check('heap', 'NPY_HEAPSORT') - self._check('merge', 'NPY_STABLESORT') # alias + self._check('quicksort', 'NPY_QUICKSORT') + self._check('heapsort', 'NPY_HEAPSORT') + self._check('mergesort', 'NPY_STABLESORT') # alias self._check('stable', 'NPY_STABLESORT') @@ -113,6 +129,8 @@ class TestSearchsideConverter(StringConverterTestCase): class TestOrderConverter(StringConverterTestCase): """ Tests of PyArray_OrderConverter """ conv = mt.run_order_converter + warn = False + def test_valid(self): self._check('c', 'NPY_CORDER') self._check('f', 'NPY_FORTRANORDER') diff --git a/numpy/core/tests/test_cpu_dispatcher.py b/numpy/core/tests/test_cpu_dispatcher.py new file mode 100644 index 000000000..8712dee1a --- /dev/null +++ b/numpy/core/tests/test_cpu_dispatcher.py @@ -0,0 +1,42 @@ +from numpy.core._multiarray_umath import __cpu_features__, __cpu_baseline__, __cpu_dispatch__ +from numpy.core import _umath_tests +from numpy.testing import assert_equal + +def test_dispatcher(): + """ + Testing the utilites of the CPU dispatcher + """ + targets = ( + "SSE2", "SSE41", "AVX2", + "VSX", "VSX2", "VSX3", + "NEON", "ASIMD", "ASIMDHP" + ) + highest_sfx = "" # no suffix for the baseline + all_sfx = [] + for feature in reversed(targets): + # skip baseline features, by the default `CCompilerOpt` do not generate separated objects + # for the baseline, just one object combined all of them via 'baseline' option + # within the configuration statments. + if feature in __cpu_baseline__: + continue + # check compiler and running machine support + if feature not in __cpu_dispatch__ or not __cpu_features__[feature]: + continue + + if not highest_sfx: + highest_sfx = "_" + feature + all_sfx.append("func" + "_" + feature) + + test = _umath_tests.test_dispatch() + assert_equal(test["func"], "func" + highest_sfx) + assert_equal(test["var"], "var" + highest_sfx) + + if highest_sfx: + assert_equal(test["func_xb"], "func" + highest_sfx) + assert_equal(test["var_xb"], "var" + highest_sfx) + else: + assert_equal(test["func_xb"], "nobase") + assert_equal(test["var_xb"], "nobase") + + all_sfx.append("func") # add the baseline + assert_equal(test["all"], all_sfx) diff --git a/numpy/core/tests/test_cpu_features.py b/numpy/core/tests/test_cpu_features.py index 337b7330c..bafa5a05f 100644 --- a/numpy/core/tests/test_cpu_features.py +++ b/numpy/core/tests/test_cpu_features.py @@ -1,8 +1,53 @@ import sys, platform, re, pytest - -from numpy.testing import assert_equal from numpy.core._multiarray_umath import __cpu_features__ +def assert_features_equal(actual, desired, fname): + __tracebackhide__ = True # Hide traceback for py.test + actual, desired = str(actual), str(desired) + if actual == desired: + return + detected = str(__cpu_features__).replace("'", "") + try: + with open("/proc/cpuinfo", "r") as fd: + cpuinfo = fd.read(2048) + except Exception as err: + cpuinfo = str(err) + + try: + import subprocess + auxv = subprocess.check_output(['/bin/true'], env=dict(LD_SHOW_AUXV="1")) + auxv = auxv.decode() + except Exception as err: + auxv = str(err) + + import textwrap + error_report = textwrap.indent( +""" +########################################### +### Extra debugging information +########################################### +------------------------------------------- +--- NumPy Detections +------------------------------------------- +%s +------------------------------------------- +--- SYS / CPUINFO +------------------------------------------- +%s.... +------------------------------------------- +--- SYS / AUXV +------------------------------------------- +%s +""" % (detected, cpuinfo, auxv), prefix='\r') + + raise AssertionError(( + "Failure Detection\n" + " NAME: '%s'\n" + " ACTUAL: %s\n" + " DESIRED: %s\n" + "%s" + ) % (fname, actual, desired, error_report)) + class AbstractTest(object): features = [] features_groups = {} @@ -12,17 +57,16 @@ class AbstractTest(object): def load_flags(self): # a hook pass - def test_features(self): self.load_flags() for gname, features in self.features_groups.items(): test_features = [self.cpu_have(f) for f in features] - assert_equal(__cpu_features__.get(gname), all(test_features)) + assert_features_equal(__cpu_features__.get(gname), all(test_features), gname) for feature_name in self.features: cpu_have = self.cpu_have(feature_name) npy_have = __cpu_features__.get(feature_name) - assert_equal(npy_have, cpu_have) + assert_features_equal(npy_have, cpu_have, feature_name) def cpu_have(self, feature_name): map_names = self.features_map.get(feature_name, feature_name) diff --git a/numpy/core/tests/test_cython.py b/numpy/core/tests/test_cython.py index 92ef09c9b..63524b269 100644 --- a/numpy/core/tests/test_cython.py +++ b/numpy/core/tests/test_cython.py @@ -15,11 +15,11 @@ except ImportError: else: from distutils.version import LooseVersion - # Cython 0.29.14 is required for Python 3.8 and there are + # Cython 0.29.21 is required for Python 3.9 and there are # other fixes in the 0.29 series that are needed even for earlier # Python versions. # Note: keep in sync with the one in pyproject.toml - required_version = LooseVersion("0.29.14") + required_version = LooseVersion("0.29.21") if LooseVersion(cython_version) < required_version: # too old or wrong cython, skip the test cython = None diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index 438d52f97..59a3954fd 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -775,6 +775,12 @@ class TestDateTime: np.dtype('m8[Y]'), np.dtype('m8[D]')) assert_raises(TypeError, np.promote_types, np.dtype('m8[M]'), np.dtype('m8[W]')) + # timedelta and float cannot be safely cast with each other + assert_raises(TypeError, np.promote_types, "float32", "m8") + assert_raises(TypeError, np.promote_types, "m8", "float32") + assert_raises(TypeError, np.promote_types, "uint64", "m8") + assert_raises(TypeError, np.promote_types, "m8", "uint64") + # timedelta <op> timedelta may overflow with big unit ranges assert_raises(OverflowError, np.promote_types, np.dtype('m8[W]'), np.dtype('m8[fs]')) @@ -2323,9 +2329,21 @@ class TestDateTime: obj_arr = np.array([None]) obj_arr[0] = a - # gh-11154: This shouldn't cause a C stack overflow - assert_raises(RecursionError, obj_arr.astype, 'M8') - assert_raises(RecursionError, obj_arr.astype, 'm8') + # At some point this caused a stack overflow (gh-11154). Now raises + # ValueError since the nested list cannot be converted to a datetime. + assert_raises(ValueError, obj_arr.astype, 'M8') + assert_raises(ValueError, obj_arr.astype, 'm8') + + @pytest.mark.parametrize("shape", [(), (1,)]) + def test_discovery_from_object_array(self, shape): + arr = np.array("2020-10-10", dtype=object).reshape(shape) + res = np.array("2020-10-10", dtype="M8").reshape(shape) + assert res.dtype == np.dtype("M8[D]") + assert_equal(arr.astype("M8"), res) + arr[...] = np.bytes_("2020-10-10") # try a numpy string type + assert_equal(arr.astype("M8"), res) + arr = arr.astype("S") + assert_equal(arr.astype("S").astype("M8"), res) @pytest.mark.parametrize("time_unit", [ "Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns", "ps", "fs", "as", diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 523638a35..431c9bb49 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -9,6 +9,7 @@ import warnings import pytest import tempfile import re +import sys import numpy as np from numpy.testing import ( @@ -313,19 +314,14 @@ class TestBinaryReprInsufficientWidthParameterForRepresentation(_DeprecationTest class TestNumericStyleTypecodes(_DeprecationTestCase): """ - Deprecate the old numeric-style dtypes, which are especially - confusing for complex types, e.g. Complex32 -> complex64. When the - deprecation cycle is complete, the check for the strings should be - removed from PyArray_DescrConverter in descriptor.c, and the - deprecated keys should not be added as capitalized aliases in - _add_aliases in numerictypes.py. + Most numeric style typecodes were previously deprecated (and removed) + in 1.20. This also deprecates the remaining ones. """ + # 2020-06-09, NumPy 1.20 def test_all_dtypes(self): - deprecated_types = [ - 'Bool', 'Complex32', 'Complex64', 'Float16', 'Float32', 'Float64', - 'Int8', 'Int16', 'Int32', 'Int64', 'Object0', 'Timedelta64', - 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Void0' - ] + deprecated_types = ['Bytes0', 'Datetime64', 'Str0'] + # Depending on intp size, either Uint32 or Uint64 is defined: + deprecated_types.append(f"U{np.dtype(np.intp).name}") for dt in deprecated_types: self.assert_deprecated(np.dtype, exceptions=(TypeError,), args=(dt,)) @@ -438,14 +434,6 @@ class TestGeneratorSum(_DeprecationTestCase): self.assert_deprecated(np.sum, args=((i for i in range(5)),)) -class TestSctypeNA(_VisibleDeprecationTestCase): - # 2018-06-24, 1.16 - def test_sctypeNA(self): - self.assert_deprecated(lambda: np.sctypeNA['?']) - self.assert_deprecated(lambda: np.typeNA['?']) - self.assert_deprecated(lambda: np.typeNA.get('?')) - - class TestPositiveOnNonNumerical(_DeprecationTestCase): # 2018-06-28, 1.16.0 def test_positive_on_non_number(self): @@ -549,6 +537,22 @@ def test_deprecate_ragged_arrays(): np.array(arg) +class TestTooDeepDeprecation(_VisibleDeprecationTestCase): + # NumPy 1.20, 2020-05-08 + # This is a bit similar to the above ragged array deprecation case. + message = re.escape("Creating an ndarray from nested sequences exceeding") + + def test_deprecation(self): + nested = [1] + for i in range(np.MAXDIMS - 1): + nested = [nested] + self.assert_not_deprecated(np.array, args=(nested,)) + self.assert_not_deprecated(np.array, + args=(nested,), kwargs=dict(dtype=object)) + + self.assert_deprecated(np.array, args=([nested],)) + + class TestToString(_DeprecationTestCase): # 2020-03-06 1.19.0 message = re.escape("tostring() is deprecated. Use tobytes() instead.") @@ -645,3 +649,47 @@ class TestIncorrectAdvancedIndexWithEmptyResult(_DeprecationTestCase): self.assert_not_deprecated(arr.__getitem__, args=(index,)) self.assert_not_deprecated(arr.__setitem__, args=(index, np.empty((2, 0, 2)))) + + +class TestNonExactMatchDeprecation(_DeprecationTestCase): + # 2020-04-22 + def test_non_exact_match(self): + arr = np.array([[3, 6, 6], [4, 5, 1]]) + # misspelt mode check + self.assert_deprecated(lambda: np.ravel_multi_index(arr, (7, 6), mode='Cilp')) + # using completely different word with first character as R + self.assert_deprecated(lambda: np.searchsorted(arr[0], 4, side='Random')) + + +class TestDeprecatedGlobals(_DeprecationTestCase): + # 2020-06-06 + @pytest.mark.skipif( + sys.version_info < (3, 7), + reason='module-level __getattr__ not supported') + def test_type_aliases(self): + # from builtins + self.assert_deprecated(lambda: np.bool) + self.assert_deprecated(lambda: np.int) + self.assert_deprecated(lambda: np.float) + self.assert_deprecated(lambda: np.complex) + self.assert_deprecated(lambda: np.object) + self.assert_deprecated(lambda: np.str) + + # from np.compat + self.assert_deprecated(lambda: np.long) + self.assert_deprecated(lambda: np.unicode) + + +class TestMatrixInOuter(_DeprecationTestCase): + # 2020-05-13 NumPy 1.20.0 + message = (r"add.outer\(\) was passed a numpy matrix as " + r"(first|second) argument.") + + def test_deprecated(self): + arr = np.array([1, 2, 3]) + m = np.array([1, 2, 3]).view(np.matrix) + self.assert_deprecated(np.add.outer, args=(m, m), num=2) + self.assert_deprecated(np.add.outer, args=(arr, m)) + self.assert_deprecated(np.add.outer, args=(m, arr)) + self.assert_not_deprecated(np.add.outer, args=(arr, arr)) + diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py index 73aa01de6..2e2b0dbe2 100644 --- a/numpy/core/tests/test_dtype.py +++ b/numpy/core/tests/test_dtype.py @@ -86,6 +86,15 @@ class TestBuiltin: assert_raises(TypeError, np.dtype, 'q8') assert_raises(TypeError, np.dtype, 'Q8') + @pytest.mark.parametrize("dtype", + ['Bool', 'Complex32', 'Complex64', 'Float16', 'Float32', 'Float64', + 'Int8', 'Int16', 'Int32', 'Int64', 'Object0', 'Timedelta64', + 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Void0', + "Float128", "Complex128"]) + def test_numeric_style_types_are_invalid(self, dtype): + with assert_raises(TypeError): + np.dtype(dtype) + @pytest.mark.parametrize( 'value', ['m8', 'M8', 'datetime64', 'timedelta64', @@ -1047,6 +1056,11 @@ def test_invalid_dtype_string(): assert_raises(TypeError, np.dtype, u'Fl\xfcgel') +def test_keyword_argument(): + # test for https://github.com/numpy/numpy/pull/16574#issuecomment-642660971 + assert np.dtype(dtype=np.float64) == np.dtype(np.float64) + + class TestFromDTypeAttribute: def test_simple(self): class dt: @@ -1324,4 +1338,3 @@ class TestFromCTypes: pair_type = np.dtype('{},{}'.format(*pair)) expected = np.dtype([('f0', pair[0]), ('f1', pair[1])]) assert_equal(pair_type, expected) - diff --git a/numpy/core/tests/test_einsum.py b/numpy/core/tests/test_einsum.py index da84735a0..c697d0c2d 100644 --- a/numpy/core/tests/test_einsum.py +++ b/numpy/core/tests/test_einsum.py @@ -94,6 +94,10 @@ class TestEinsum: b = np.ones((3, 4, 5)) np.einsum('aabcb,abc', a, b) + # Check order kwarg, asanyarray allows 1d to pass through + assert_raises(ValueError, np.einsum, "i->i", np.arange(6).reshape(-1, 1), + optimize=do_opt, order='d') + def test_einsum_views(self): # pass-through for do_opt in [True, False]: @@ -876,6 +880,41 @@ class TestEinsum: g = np.arange(64).reshape(2, 4, 8) self.optimize_compare('obk,ijk->ioj', operands=[g, g]) + def test_output_order(self): + # Ensure output order is respected for optimize cases, the below + # conraction should yield a reshaped tensor view + # gh-16415 + + a = np.ones((2, 3, 5), order='F') + b = np.ones((4, 3), order='F') + + for opt in [True, False]: + tmp = np.einsum('...ft,mf->...mt', a, b, order='a', optimize=opt) + assert_(tmp.flags.f_contiguous) + + tmp = np.einsum('...ft,mf->...mt', a, b, order='f', optimize=opt) + assert_(tmp.flags.f_contiguous) + + tmp = np.einsum('...ft,mf->...mt', a, b, order='c', optimize=opt) + assert_(tmp.flags.c_contiguous) + + tmp = np.einsum('...ft,mf->...mt', a, b, order='k', optimize=opt) + assert_(tmp.flags.c_contiguous is False) + assert_(tmp.flags.f_contiguous is False) + + tmp = np.einsum('...ft,mf->...mt', a, b, optimize=opt) + assert_(tmp.flags.c_contiguous is False) + assert_(tmp.flags.f_contiguous is False) + + c = np.ones((4, 3), order='C') + for opt in [True, False]: + tmp = np.einsum('...ft,mf->...mt', a, c, order='a', optimize=opt) + assert_(tmp.flags.c_contiguous) + + d = np.ones((2, 3, 5), order='C') + for opt in [True, False]: + tmp = np.einsum('...ft,mf->...mt', d, c, order='a', optimize=opt) + assert_(tmp.flags.c_contiguous) class TestEinsumPath: def build_operands(self, string, size_dict=global_size_dict): diff --git a/numpy/core/tests/test_function_base.py b/numpy/core/tests/test_function_base.py index 2197ef0cd..62a9772c8 100644 --- a/numpy/core/tests/test_function_base.py +++ b/numpy/core/tests/test_function_base.py @@ -1,6 +1,6 @@ from numpy import ( logspace, linspace, geomspace, dtype, array, sctypes, arange, isnan, - ndarray, sqrt, nextafter, stack + ndarray, sqrt, nextafter, stack, errstate ) from numpy.testing import ( assert_, assert_equal, assert_raises, assert_array_equal, assert_allclose, @@ -113,6 +113,40 @@ class TestGeomspace: assert_array_equal(y, [-100, -10, -1]) assert_array_equal(y.imag, 0) + def test_boundaries_match_start_and_stop_exactly(self): + # make sure that the boundaries of the returned array exactly + # equal 'start' and 'stop' - this isn't obvious because + # np.exp(np.log(x)) isn't necessarily exactly equal to x + start = 0.3 + stop = 20.3 + + y = geomspace(start, stop, num=1) + assert_equal(y[0], start) + + y = geomspace(start, stop, num=1, endpoint=False) + assert_equal(y[0], start) + + y = geomspace(start, stop, num=3) + assert_equal(y[0], start) + assert_equal(y[-1], stop) + + y = geomspace(start, stop, num=3, endpoint=False) + assert_equal(y[0], start) + + def test_nan_interior(self): + with errstate(invalid='ignore'): + y = geomspace(-3, 3, num=4) + + assert_equal(y[0], -3.0) + assert_(isnan(y[1:-1]).all()) + assert_equal(y[3], 3.0) + + with errstate(invalid='ignore'): + y = geomspace(-3, 3, num=4, endpoint=False) + + assert_equal(y[0], -3.0) + assert_(isnan(y[1:]).all()) + def test_complex(self): # Purely imaginary y = geomspace(1j, 16j, num=5) diff --git a/numpy/core/tests/test_indexing.py b/numpy/core/tests/test_indexing.py index 4bb5cb11a..1069cbe8d 100644 --- a/numpy/core/tests/test_indexing.py +++ b/numpy/core/tests/test_indexing.py @@ -370,6 +370,20 @@ class TestIndexing: a[...] = s assert_((a == 1).all()) + def test_array_like_values(self): + # Similar to the above test, but use a memoryview instead + a = np.zeros((5, 5)) + s = np.arange(25, dtype=np.float64).reshape(5, 5) + + a[[0, 1, 2, 3, 4], :] = memoryview(s) + assert_array_equal(a, s) + + a[:, [0, 1, 2, 3, 4]] = memoryview(s) + assert_array_equal(a, s) + + a[...] = memoryview(s) + assert_array_equal(a, s) + def test_subclass_writeable(self): d = np.rec.array([('NGC1001', 11), ('NGC1002', 1.), ('NGC1003', 1.)], dtype=[('target', 'S20'), ('V_mag', '>f4')]) @@ -524,6 +538,15 @@ class TestIndexing: arr[slices] = 10 assert_array_equal(arr, 10.) + def test_character_assignment(self): + # This is an example a function going through CopyObject which + # used to have an untested special path for scalars + # (the character special dtype case, should be deprecated probably) + arr = np.zeros((1, 5), dtype="c") + arr[0] = np.str_("asdfg") # must assign as a sequence + assert_array_equal(arr[0], np.array("asdfg", dtype="c")) + assert arr[0, 1] == b"s" # make sure not all were set to "a" for both + class TestFieldIndexing: def test_scalar_return_type(self): # Field access on an array should return an array, even if it diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py index 1a8268eb8..b7d4a6a92 100644 --- a/numpy/core/tests/test_multiarray.py +++ b/numpy/core/tests/test_multiarray.py @@ -1012,6 +1012,8 @@ class TestCreation: with assert_raises(ValueError): a[:] = C() # Segfault! + np.array(C()) == list(C()) + def test_failed_len_sequence(self): # gh-7393 class A: @@ -1369,6 +1371,11 @@ class TestStructured: a = np.array([(1,2)], dtype=[('a', 'i4'), ('b', 'i4')]) a[['a', 'b']] = a[['b', 'a']] assert_equal(a[0].item(), (2,1)) + + def test_scalar_assignment(self): + with assert_raises(ValueError): + arr = np.arange(25).reshape(5, 5) + arr.itemset(3) def test_structuredscalar_indexing(self): # test gh-7262 @@ -1383,6 +1390,21 @@ class TestStructured: assert_raises(ValueError, lambda : a[['b','b']]) # field exists, but repeated a[['b','c']] # no exception + def test_structured_asarray_is_view(self): + # A scalar viewing an array preserves its view even when creating a + # new array. This test documents behaviour, it may not be the best + # desired behaviour. + arr = np.array([1], dtype="i,i") + scalar = arr[0] + assert not scalar.flags.owndata # view into the array + assert np.asarray(scalar).base is scalar + # But never when a dtype is passed in: + assert np.asarray(scalar, dtype=scalar.dtype).base is None + # A scalar which owns its data does not have this property. + # It is not easy to create one, one method is to use pickle: + scalar = pickle.loads(pickle.dumps(scalar)) + assert scalar.flags.owndata + assert np.asarray(scalar).base is None class TestBool: def test_test_interning(self): @@ -2153,10 +2175,10 @@ class TestMethods: # check double a = np.array([0, 1, np.nan]) msg = "Test real searchsorted with nans, side='l'" - b = a.searchsorted(a, side='l') + b = a.searchsorted(a, side='left') assert_equal(b, np.arange(3), msg) msg = "Test real searchsorted with nans, side='r'" - b = a.searchsorted(a, side='r') + b = a.searchsorted(a, side='right') assert_equal(b, np.arange(1, 4), msg) # check keyword arguments a.searchsorted(v=1) @@ -2165,10 +2187,10 @@ class TestMethods: a.real += [0, 0, 1, 1, 0, 1, np.nan, np.nan, np.nan] a.imag += [0, 1, 0, 1, np.nan, np.nan, 0, 1, np.nan] msg = "Test complex searchsorted with nans, side='l'" - b = a.searchsorted(a, side='l') + b = a.searchsorted(a, side='left') assert_equal(b, np.arange(9), msg) msg = "Test complex searchsorted with nans, side='r'" - b = a.searchsorted(a, side='r') + b = a.searchsorted(a, side='right') assert_equal(b, np.arange(1, 10), msg) msg = "Test searchsorted with little endian, side='l'" a = np.array([0, 128], dtype='<i4') @@ -2181,21 +2203,21 @@ class TestMethods: # Check 0 elements a = np.ones(0) - b = a.searchsorted([0, 1, 2], 'l') + b = a.searchsorted([0, 1, 2], 'left') assert_equal(b, [0, 0, 0]) - b = a.searchsorted([0, 1, 2], 'r') + b = a.searchsorted([0, 1, 2], 'right') assert_equal(b, [0, 0, 0]) a = np.ones(1) # Check 1 element - b = a.searchsorted([0, 1, 2], 'l') + b = a.searchsorted([0, 1, 2], 'left') assert_equal(b, [0, 0, 1]) - b = a.searchsorted([0, 1, 2], 'r') + b = a.searchsorted([0, 1, 2], 'right') assert_equal(b, [0, 1, 1]) # Check all elements equal a = np.ones(2) - b = a.searchsorted([0, 1, 2], 'l') + b = a.searchsorted([0, 1, 2], 'left') assert_equal(b, [0, 0, 2]) - b = a.searchsorted([0, 1, 2], 'r') + b = a.searchsorted([0, 1, 2], 'right') assert_equal(b, [0, 2, 2]) # Test searching unaligned array @@ -2204,21 +2226,21 @@ class TestMethods: unaligned = aligned[1:].view(a.dtype) unaligned[:] = a # Test searching unaligned array - b = unaligned.searchsorted(a, 'l') + b = unaligned.searchsorted(a, 'left') assert_equal(b, a) - b = unaligned.searchsorted(a, 'r') + b = unaligned.searchsorted(a, 'right') assert_equal(b, a + 1) # Test searching for unaligned keys - b = a.searchsorted(unaligned, 'l') + b = a.searchsorted(unaligned, 'left') assert_equal(b, a) - b = a.searchsorted(unaligned, 'r') + b = a.searchsorted(unaligned, 'right') assert_equal(b, a + 1) # Test smart resetting of binsearch indices a = np.arange(5) - b = a.searchsorted([6, 5, 4], 'l') + b = a.searchsorted([6, 5, 4], 'left') assert_equal(b, [5, 5, 4]) - b = a.searchsorted([6, 5, 4], 'r') + b = a.searchsorted([6, 5, 4], 'right') assert_equal(b, [5, 5, 5]) # Test all type specific binary search functions @@ -2233,16 +2255,16 @@ class TestMethods: else: a = np.arange(0, 5, dtype=dt) out = np.arange(5) - b = a.searchsorted(a, 'l') + b = a.searchsorted(a, 'left') assert_equal(b, out) - b = a.searchsorted(a, 'r') + b = a.searchsorted(a, 'right') assert_equal(b, out + 1) # Test empty array, use a fresh array to get warnings in # valgrind if access happens. e = np.ndarray(shape=0, buffer=b'', dtype=dt) - b = e.searchsorted(a, 'l') + b = e.searchsorted(a, 'left') assert_array_equal(b, np.zeros(len(a), dtype=np.intp)) - b = a.searchsorted(e, 'l') + b = a.searchsorted(e, 'left') assert_array_equal(b, np.zeros(0, dtype=np.intp)) def test_searchsorted_unicode(self): @@ -2297,9 +2319,9 @@ class TestMethods: s = a.argsort() k = [0, 1, 2, 3, 5] expected = [0, 20, 40, 60, 80] - assert_equal(a.searchsorted(k, side='l', sorter=s), expected) + assert_equal(a.searchsorted(k, side='left', sorter=s), expected) expected = [20, 40, 60, 80, 100] - assert_equal(a.searchsorted(k, side='r', sorter=s), expected) + assert_equal(a.searchsorted(k, side='right', sorter=s), expected) # Test searching unaligned array keys = np.arange(10) @@ -2310,15 +2332,15 @@ class TestMethods: unaligned = aligned[1:].view(a.dtype) # Test searching unaligned array unaligned[:] = a - b = unaligned.searchsorted(keys, 'l', s) + b = unaligned.searchsorted(keys, 'left', s) assert_equal(b, keys) - b = unaligned.searchsorted(keys, 'r', s) + b = unaligned.searchsorted(keys, 'right', s) assert_equal(b, keys + 1) # Test searching for unaligned keys unaligned[:] = keys - b = a.searchsorted(unaligned, 'l', s) + b = a.searchsorted(unaligned, 'left', s) assert_equal(b, keys) - b = a.searchsorted(unaligned, 'r', s) + b = a.searchsorted(unaligned, 'right', s) assert_equal(b, keys + 1) # Test all type specific indirect binary search functions @@ -2339,16 +2361,16 @@ class TestMethods: # from np.intp in all platforms, to check for #4698 s = np.array([4, 2, 3, 0, 1], dtype=np.int16) out = np.array([3, 4, 1, 2, 0], dtype=np.intp) - b = a.searchsorted(a, 'l', s) + b = a.searchsorted(a, 'left', s) assert_equal(b, out) - b = a.searchsorted(a, 'r', s) + b = a.searchsorted(a, 'right', s) assert_equal(b, out + 1) # Test empty array, use a fresh array to get warnings in # valgrind if access happens. e = np.ndarray(shape=0, buffer=b'', dtype=dt) - b = e.searchsorted(a, 'l', s[:0]) + b = e.searchsorted(a, 'left', s[:0]) assert_array_equal(b, np.zeros(len(a), dtype=np.intp)) - b = a.searchsorted(e, 'l', s) + b = a.searchsorted(e, 'left', s) assert_array_equal(b, np.zeros(0, dtype=np.intp)) # Test non-contiguous sorter array @@ -2358,9 +2380,9 @@ class TestMethods: srt[::2] = [4, 2, 3, 0, 1] s = srt[::2] out = np.array([3, 4, 1, 2, 0], dtype=np.intp) - b = a.searchsorted(a, 'l', s) + b = a.searchsorted(a, 'left', s) assert_equal(b, out) - b = a.searchsorted(a, 'r', s) + b = a.searchsorted(a, 'right', s) assert_equal(b, out + 1) def test_searchsorted_return_type(self): @@ -2370,10 +2392,10 @@ class TestMethods: a = np.arange(5).view(A) b = np.arange(1, 3).view(A) s = np.arange(5).view(A) - assert_(not isinstance(a.searchsorted(b, 'l'), A)) - assert_(not isinstance(a.searchsorted(b, 'r'), A)) - assert_(not isinstance(a.searchsorted(b, 'l', s), A)) - assert_(not isinstance(a.searchsorted(b, 'r', s), A)) + assert_(not isinstance(a.searchsorted(b, 'left'), A)) + assert_(not isinstance(a.searchsorted(b, 'right'), A)) + assert_(not isinstance(a.searchsorted(b, 'left', s), A)) + assert_(not isinstance(a.searchsorted(b, 'right', s), A)) def test_argpartition_out_of_range(self): # Test out of range values in kth raise an error, gh-5469 @@ -4702,6 +4724,10 @@ class TestIO: e = np.array([-25041670086757, 104783749223640], dtype=np.int64) assert_array_equal(d, e) + def test_fromstring_count0(self): + d = np.fromstring("1,2", sep=",", dtype=np.int64, count=0) + assert d.shape == (0,) + def test_empty_files_binary(self): with open(self.filename, 'w') as f: pass diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py index 2a87ffaf8..badf48b33 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -2521,7 +2521,7 @@ class TestCreationFuncs: self.check_function(np.zeros) def test_ones(self): - self.check_function(np.zeros) + self.check_function(np.ones) def test_empty(self): self.check_function(np.empty) diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py index 96a6d810f..170d20157 100644 --- a/numpy/core/tests/test_regression.py +++ b/numpy/core/tests/test_regression.py @@ -42,13 +42,6 @@ class TestRegression: b = pickle.load(f) assert_array_equal(a, b) - def test_typeNA(self): - # Issue gh-515 - with suppress_warnings() as sup: - sup.filter(np.VisibleDeprecationWarning) - assert_equal(np.typeNA[np.int64], 'Int64') - assert_equal(np.typeNA[np.uint64], 'UInt64') - def test_dtype_names(self): # Ticket #35 # Should succeed @@ -2332,6 +2325,10 @@ class TestRegression: # allowed as a special case due to existing use, see gh-2798 a = np.ones(1, dtype=('O', [('name', 'O')])) assert_equal(a[0], 1) + # In particular, the above union dtype (and union dtypes in general) + # should mainly behave like the main (object) dtype: + assert a[0] is a.item() + assert type(a[0]) is int def test_correct_hash_dict(self): # gh-8887 - __hash__ would be None despite tp_hash being set @@ -2457,7 +2454,8 @@ class TestRegression: class T: __array_interface__ = {} - np.array([T()]) + with assert_raises(ValueError): + np.array([T()]) def test_2d__array__shape(self): class T(object): diff --git a/numpy/core/tests/test_scalar_ctors.py b/numpy/core/tests/test_scalar_ctors.py index 7645a0853..7e933537d 100644 --- a/numpy/core/tests/test_scalar_ctors.py +++ b/numpy/core/tests/test_scalar_ctors.py @@ -65,7 +65,7 @@ class TestExtraArgs: def test_bool(self): with pytest.raises(TypeError): - np.bool(False, garbage=True) + np.bool_(False, garbage=True) def test_void(self): with pytest.raises(TypeError): @@ -79,3 +79,37 @@ class TestFromInt: def test_uint64_from_negative(self): assert_equal(np.uint64(-2), np.uint64(18446744073709551614)) + + +int_types = [np.byte, np.short, np.intc, np.int_, np.longlong] +uint_types = [np.ubyte, np.ushort, np.uintc, np.uint, np.ulonglong] +float_types = [np.half, np.single, np.double, np.longdouble] +cfloat_types = [np.csingle, np.cdouble, np.clongdouble] + + +class TestArrayFromScalar: + """ gh-15467 """ + + def _do_test(self, t1, t2): + x = t1(2) + arr = np.array(x, dtype=t2) + # type should be preserved exactly + if t2 is None: + assert arr.dtype.type is t1 + else: + assert arr.dtype.type is t2 + + @pytest.mark.parametrize('t1', int_types + uint_types) + @pytest.mark.parametrize('t2', int_types + uint_types + [None]) + def test_integers(self, t1, t2): + return self._do_test(t1, t2) + + @pytest.mark.parametrize('t1', float_types) + @pytest.mark.parametrize('t2', float_types + [None]) + def test_reals(self, t1, t2): + return self._do_test(t1, t2) + + @pytest.mark.parametrize('t1', cfloat_types) + @pytest.mark.parametrize('t2', cfloat_types + [None]) + def test_complex(self, t1, t2): + return self._do_test(t1, t2) diff --git a/numpy/core/tests/test_scalarbuffer.py b/numpy/core/tests/test_scalarbuffer.py index b1c1bbbb1..574c56864 100644 --- a/numpy/core/tests/test_scalarbuffer.py +++ b/numpy/core/tests/test_scalarbuffer.py @@ -2,6 +2,7 @@ Test scalar buffer interface adheres to PEP 3118 """ import numpy as np +from numpy.core._rational_tests import rational import pytest from numpy.testing import assert_, assert_equal, assert_raises @@ -117,3 +118,8 @@ class TestScalarPEP3118: code_points = np.frombuffer(v, dtype='i4') assert_equal(code_points, [ord(c) for c in s]) + + def test_user_scalar_fails_buffer(self): + r = rational(1) + with assert_raises(TypeError): + memoryview(r) diff --git a/numpy/core/tests/test_shape_base.py b/numpy/core/tests/test_shape_base.py index 546ecf001..94a916193 100644 --- a/numpy/core/tests/test_shape_base.py +++ b/numpy/core/tests/test_shape_base.py @@ -8,7 +8,7 @@ from numpy.core.shape_base import (_block_dispatcher, _block_setup, _block_concatenate, _block_slicing) from numpy.testing import ( assert_, assert_raises, assert_array_equal, assert_equal, - assert_raises_regex, assert_warns + assert_raises_regex, assert_warns, IS_PYPY ) @@ -320,6 +320,19 @@ class TestConcatenate: assert_(out is rout) assert_equal(res, rout) + @pytest.mark.skipif(IS_PYPY, reason="PYPY handles sq_concat, nb_add differently than cpython") + def test_operator_concat(self): + import operator + a = array([1, 2]) + b = array([3, 4]) + n = [1,2] + res = array([1, 2, 3, 4]) + assert_raises(TypeError, operator.concat, a, b) + assert_raises(TypeError, operator.concat, a, n) + assert_raises(TypeError, operator.concat, n, a) + assert_raises(TypeError, operator.concat, a, 1) + assert_raises(TypeError, operator.concat, 1, a) + def test_bad_out_shape(self): a = array([1, 2]) b = array([3, 4]) diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index 91acd6ac3..ae72687ca 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -23,7 +23,7 @@ def on_powerpc(): def bad_arcsinh(): - """The blacklisted trig functions are not accurate on aarch64 for + """The blocklisted trig functions are not accurate on aarch64 for complex256. Rather than dig through the actual problem skip the test. This should be fixed when we can move past glibc2.17 which is the version in manylinux2014 @@ -657,6 +657,19 @@ class TestLog: yf = np.array(y, dtype=dt)*log2_ assert_almost_equal(np.log(xf), yf) + def test_log_strides(self): + np.random.seed(42) + strides = np.array([-4,-3,-2,-1,1,2,3,4]) + sizes = np.arange(2,100) + for ii in sizes: + x_f64 = np.float64(np.random.uniform(low=0.01, high=100.0,size=ii)) + x_special = x_f64.copy() + x_special[3:-1:4] = 1.0 + y_true = np.log(x_f64) + y_special = np.log(x_special) + for jj in strides: + assert_array_almost_equal_nulp(np.log(x_f64[::jj]), y_true[::jj], nulp=2) + assert_array_almost_equal_nulp(np.log(x_special[::jj]), y_special[::jj], nulp=2) class TestExp: def test_exp_values(self): @@ -890,15 +903,17 @@ class TestAVXFloat32Transcendental: sizes = np.arange(2,100) for ii in sizes: x_f32 = np.float32(np.random.uniform(low=0.01,high=88.1,size=ii)) + x_f32_large = x_f32.copy() + x_f32_large[3:-1:4] = 120000.0 exp_true = np.exp(x_f32) log_true = np.log(x_f32) - sin_true = np.sin(x_f32) - cos_true = np.cos(x_f32) + sin_true = np.sin(x_f32_large) + cos_true = np.cos(x_f32_large) for jj in strides: assert_array_almost_equal_nulp(np.exp(x_f32[::jj]), exp_true[::jj], nulp=2) assert_array_almost_equal_nulp(np.log(x_f32[::jj]), log_true[::jj], nulp=2) - assert_array_almost_equal_nulp(np.sin(x_f32[::jj]), sin_true[::jj], nulp=2) - assert_array_almost_equal_nulp(np.cos(x_f32[::jj]), cos_true[::jj], nulp=2) + assert_array_almost_equal_nulp(np.sin(x_f32_large[::jj]), sin_true[::jj], nulp=2) + assert_array_almost_equal_nulp(np.cos(x_f32_large[::jj]), cos_true[::jj], nulp=2) class TestLogAddExp(_FilterInvalids): def test_logaddexp_values(self): @@ -3268,3 +3283,39 @@ def test_outer_subclass_preserve(arr): class foo(np.ndarray): pass actual = np.multiply.outer(arr.view(foo), arr.view(foo)) assert actual.__class__.__name__ == 'foo' + +def test_outer_bad_subclass(): + class BadArr1(np.ndarray): + def __array_finalize__(self, obj): + # The outer call reshapes to 3 dims, try to do a bad reshape. + if self.ndim == 3: + self.shape = self.shape + (1,) + + def __array_prepare__(self, obj, context=None): + return obj + + class BadArr2(np.ndarray): + def __array_finalize__(self, obj): + if isinstance(obj, BadArr2): + # outer inserts 1-sized dims. In that case disturb them. + if self.shape[-1] == 1: + self.shape = self.shape[::-1] + + def __array_prepare__(self, obj, context=None): + return obj + + for cls in [BadArr1, BadArr2]: + arr = np.ones((2, 3)).view(cls) + with assert_raises(TypeError) as a: + # The first array gets reshaped (not the second one) + np.add.outer(arr, [1, 2]) + + # This actually works, since we only see the reshaping error: + arr = np.ones((2, 3)).view(cls) + assert type(np.add.outer([1, 2], arr)) is cls + +def test_outer_exceeds_maxdims(): + deep = np.ones((1,) * 17) + with assert_raises(ValueError): + np.add.outer(deep, deep) + diff --git a/numpy/core/tests/test_umath_accuracy.py b/numpy/core/tests/test_umath_accuracy.py index e3c2eb025..33080edbb 100644 --- a/numpy/core/tests/test_umath_accuracy.py +++ b/numpy/core/tests/test_umath_accuracy.py @@ -57,9 +57,3 @@ class TestAccuracy: outval = outval[perm] maxulperr = data_subset['ulperr'].max() assert_array_max_ulp(npfunc(inval), outval, maxulperr) - - def test_ignore_nan_ulperror(self): - # Ignore ULP differences between various NAN's - nan1_f32 = np.array(str_to_float('0xffffffff'), dtype=np.float32) - nan2_f32 = np.array(str_to_float('0x7fddbfbf'), dtype=np.float32) - assert_array_max_ulp(nan1_f32, nan2_f32, 0) diff --git a/numpy/core/tests/test_umath_complex.py b/numpy/core/tests/test_umath_complex.py index a21158420..90a349da1 100644 --- a/numpy/core/tests/test_umath_complex.py +++ b/numpy/core/tests/test_umath_complex.py @@ -545,25 +545,25 @@ class TestSpecialComplexAVX(object): @pytest.mark.parametrize("stride", [-4,-2,-1,1,2,4]) @pytest.mark.parametrize("astype", [np.complex64, np.complex128]) def test_array(self, stride, astype): - arr = np.array([np.complex(np.nan , np.nan), - np.complex(np.nan , np.inf), - np.complex(np.inf , np.nan), - np.complex(np.inf , np.inf), - np.complex(0. , np.inf), - np.complex(np.inf , 0.), - np.complex(0. , 0.), - np.complex(0. , np.nan), - np.complex(np.nan , 0.)], dtype=astype) + arr = np.array([complex(np.nan , np.nan), + complex(np.nan , np.inf), + complex(np.inf , np.nan), + complex(np.inf , np.inf), + complex(0. , np.inf), + complex(np.inf , 0.), + complex(0. , 0.), + complex(0. , np.nan), + complex(np.nan , 0.)], dtype=astype) abs_true = np.array([np.nan, np.inf, np.inf, np.inf, np.inf, np.inf, 0., np.nan, np.nan], dtype=arr.real.dtype) - sq_true = np.array([np.complex(np.nan, np.nan), - np.complex(np.nan, np.nan), - np.complex(np.nan, np.nan), - np.complex(np.nan, np.inf), - np.complex(-np.inf, np.nan), - np.complex(np.inf, np.nan), - np.complex(0., 0.), - np.complex(np.nan, np.nan), - np.complex(np.nan, np.nan)], dtype=astype) + sq_true = np.array([complex(np.nan, np.nan), + complex(np.nan, np.nan), + complex(np.nan, np.nan), + complex(np.nan, np.inf), + complex(-np.inf, np.nan), + complex(np.inf, np.nan), + complex(0., 0.), + complex(np.nan, np.nan), + complex(np.nan, np.nan)], dtype=astype) assert_equal(np.abs(arr[::stride]), abs_true[::stride]) with np.errstate(invalid='ignore'): assert_equal(np.square(arr[::stride]), sq_true[::stride]) @@ -577,3 +577,34 @@ class TestComplexAbsoluteAVX(object): arr = np.ones(arraysize, dtype=astype) abs_true = np.ones(arraysize, dtype=arr.real.dtype) assert_equal(np.abs(arr[::stride]), abs_true[::stride]) + +# Testcase taken as is from https://github.com/numpy/numpy/issues/16660 +class TestComplexAbsoluteMixedDTypes(object): + @pytest.mark.parametrize("stride", [-4,-3,-2,-1,1,2,3,4]) + @pytest.mark.parametrize("astype", [np.complex64, np.complex128]) + @pytest.mark.parametrize("func", ['abs', 'square', 'conjugate']) + + def test_array(self, stride, astype, func): + dtype = [('template_id', '<i8'), ('bank_chisq','<f4'), + ('bank_chisq_dof','<i8'), ('chisq', '<f4'), ('chisq_dof','<i8'), + ('cont_chisq', '<f4'), ('psd_var_val', '<f4'), ('sg_chisq','<f4'), + ('mycomplex', astype), ('time_index', '<i8')] + vec = np.array([ + (0, 0., 0, -31.666483, 200, 0., 0., 1. , 3.0+4.0j , 613090), + (1, 0., 0, 260.91525 , 42, 0., 0., 1. , 5.0+12.0j , 787315), + (1, 0., 0, 52.15155 , 42, 0., 0., 1. , 8.0+15.0j , 806641), + (1, 0., 0, 52.430195, 42, 0., 0., 1. , 7.0+24.0j , 1363540), + (2, 0., 0, 304.43646 , 58, 0., 0., 1. , 20.0+21.0j , 787323), + (3, 0., 0, 299.42108 , 52, 0., 0., 1. , 12.0+35.0j , 787332), + (4, 0., 0, 39.4836 , 28, 0., 0., 9.182192, 9.0+40.0j , 787304), + (4, 0., 0, 76.83787 , 28, 0., 0., 1. , 28.0+45.0j, 1321869), + (5, 0., 0, 143.26366 , 24, 0., 0., 10.996129, 11.0+60.0j , 787299)], dtype=dtype) + myfunc = getattr(np, func) + a = vec['mycomplex'] + g = myfunc(a[::stride]) + + b = vec['mycomplex'].copy() + h = myfunc(b[::stride]) + + assert_array_max_ulp(h.real, g.real, 1) + assert_array_max_ulp(h.imag, g.imag, 1) diff --git a/numpy/distutils/ccompiler.py b/numpy/distutils/ccompiler.py index 9ea083774..106436e64 100644 --- a/numpy/distutils/ccompiler.py +++ b/numpy/distutils/ccompiler.py @@ -443,14 +443,6 @@ def CCompiler_show_customization(self): Printing is only done if the distutils log threshold is < 2. """ - if 0: - for attrname in ['include_dirs', 'define', 'undef', - 'libraries', 'library_dirs', - 'rpath', 'link_objects']: - attr = getattr(self, attrname, None) - if not attr: - continue - log.info("compiler '%s' is set to %s" % (attrname, attr)) try: self.get_version() except Exception: diff --git a/numpy/distutils/ccompiler_opt.py b/numpy/distutils/ccompiler_opt.py new file mode 100644 index 000000000..85dc2f1e8 --- /dev/null +++ b/numpy/distutils/ccompiler_opt.py @@ -0,0 +1,2464 @@ +"""Provides the `CCompilerOpt` class, used for handling the CPU/hardware +optimization, starting from parsing the command arguments, to managing the +relation between the CPU baseline and dispatch-able features, +also generating the required C headers and ending with compiling +the sources with proper compiler's flags. + +`CCompilerOpt` doesn't provide runtime detection for the CPU features, +instead only focuses on the compiler side, but it creates abstract C headers +that can be used later for the final runtime dispatching process.""" + +import sys, io, os, re, textwrap, pprint, inspect, atexit, subprocess + +class _Config: + """An abstract class holds all configurable attributes of `CCompilerOpt`, + these class attributes can be used to change the default behavior + of `CCompilerOpt` in order to fit other requirements. + + Attributes + ---------- + conf_nocache : bool + Set True to disable memory and file cache. + Default is False. + + conf_noopt : bool + Set True to forces the optimization to be disabled, + in this case `CCompilerOpt` tends to generate all + expected headers in order to 'not' break the build. + Default is False. + + conf_cache_factors : list + Add extra factors to the primary caching factors. The caching factors + are utilized to determine if there are changes had happened that + requires to discard the cache and re-updating it. The primary factors + are the arguments of `CCompilerOpt` and `CCompiler`'s properties(type, flags, etc). + Default is list of two items, containing the time of last modification + of `ccompiler_opt` and value of attribute "conf_noopt" + + conf_tmp_path : str, + The path of temporary directory. Default is auto-created + temporary directory via ``tempfile.mkdtemp()``. + + conf_check_path : str + The path of testing files. Each added CPU feature must have a + **C** source file contains at least one intrinsic or instruction that + related to this feature, so it can be tested against the compiler. + Default is ``./distutils/checks``. + + conf_target_groups : dict + Extra tokens that can be reached from dispatch-able sources through + the special mark ``@targets``. Default is an empty dictionary. + + **Notes**: + - case-insensitive for tokens and group names + - sign '#' must stick in the begin of group name and only within ``@targets`` + + **Example**: + .. code-block:: console + + $ "@targets #avx_group other_tokens" > group_inside.c + + >>> CCompilerOpt.conf_target_groups["avx_group"] = \\ + "$werror $maxopt avx2 avx512f avx512_skx" + >>> cco = CCompilerOpt(cc_instance) + >>> cco.try_dispatch(["group_inside.c"]) + + conf_c_prefix : str + The prefix of public C definitions. Default is ``"NPY_"``. + + conf_c_prefix_ : str + The prefix of internal C definitions. Default is ``"NPY__"``. + + conf_cc_flags : dict + Nested dictionaries defining several compiler flags + that linked to some major functions, the main key + represent the compiler name and sub-keys represent + flags names. Default is already covers all supported + **C** compilers. + + Sub-keys explained as follows: + + "native": str or None + used by argument option `native`, to detect the current + machine support via the compiler. + "werror": str or None + utilized to treat warning as errors during testing CPU features + against the compiler and also for target's policy `$werror` + via dispatch-able sources. + "maxopt": str or None + utilized for target's policy '$maxopt' and the value should + contains the maximum acceptable optimization by the compiler. + e.g. in gcc `'-O3'` + + **Notes**: + * case-sensitive for compiler names and flags + * use space to separate multiple flags + * any flag will tested against the compiler and it will skipped + if it's not applicable. + + conf_min_features : dict + A dictionary defines the used CPU features for + argument option `'min'`, the key represent the CPU architecture + name e.g. `'x86'`. Default values provide the best effort + on wide range of users platforms. + + **Note**: case-sensitive for architecture names. + + conf_features : dict + Nested dictionaries used for identifying the CPU features. + the primary key is represented as a feature name or group name + that gathers several features. Default values covers all + supported features but without the major options like "flags", + these undefined options handle it by method `conf_features_partial()`. + Default value is covers almost all CPU features for *X86*, *IBM/Power64* + and *ARM 7/8*. + + Sub-keys explained as follows: + + "implies" : str or list, optional, + List of CPU feature names to be implied by it, + the feature name must be defined within `conf_features`. + Default is None. + + "flags": str or list, optional + List of compiler flags. Default is None. + + "detect": str or list, optional + List of CPU feature names that required to be detected + in runtime. By default, its the feature name or features + in "group" if its specified. + + "implies_detect": bool, optional + If True, all "detect" of implied features will be combined. + Default is True. see `feature_detect()`. + + "group": str or list, optional + Same as "implies" but doesn't require the feature name to be + defined within `conf_features`. + + "interest": int, required + a key for sorting CPU features + + "headers": str or list, optional + intrinsics C header file + + "disable": str, optional + force disable feature, the string value should contains the + reason of disabling. + + "autovec": bool or None, optional + True or False to declare that CPU feature can be auto-vectorized + by the compiler. + By default(None), treated as True if the feature contains at + least one applicable flag. see `feature_can_autovec()` + + **NOTES**: + * space can be used as separator with options that supports "str or list" + * case-sensitive for all values and feature name must be in upper-case. + * if flags aren't applicable, its will skipped rather than disable the + CPU feature + * the CPU feature will disabled if the compiler fail to compile + the test file + """ + conf_nocache = False + conf_noopt = False + conf_cache_factors = None + conf_tmp_path = None + conf_check_path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "checks" + ) + conf_target_groups = {} + conf_c_prefix = 'NPY_' + conf_c_prefix_ = 'NPY__' + conf_cc_flags = dict( + gcc = dict( + # native should always fail on arm and ppc64, + # native usually works only with x86 + native = '-march=native', + opt = '-O3', + werror = '-Werror' + ), + clang = dict( + native = '-march=native', + opt = "-O3", + werror = '-Werror' + ), + icc = dict( + native = '-xHost', + opt = '-O3', + werror = '-Werror' + ), + iccw = dict( + native = '/QxHost', + opt = '/O3', + werror = '/Werror' + ), + msvc = dict( + native = None, + opt = '/O2', + werror = '/WX' + ) + ) + conf_min_features = dict( + x86 = "SSE SSE2", + x64 = "SSE SSE2 SSE3", + ppc64 = '', # play it safe + ppc64le = "VSX VSX2", + armhf = '', # play it safe + aarch64 = "NEON NEON_FP16 NEON_VFPV4 ASIMD" + ) + conf_features = dict( + # X86 + SSE = dict( + interest=1, headers="xmmintrin.h", + # enabling SSE without SSE2 is useless also + # it's non-optional for x86_64 + implies="SSE2" + ), + SSE2 = dict(interest=2, implies="SSE", headers="emmintrin.h"), + SSE3 = dict(interest=3, implies="SSE2", headers="pmmintrin.h"), + SSSE3 = dict(interest=4, implies="SSE3", headers="tmmintrin.h"), + SSE41 = dict(interest=5, implies="SSSE3", headers="smmintrin.h"), + POPCNT = dict(interest=6, implies="SSE41", headers="popcntintrin.h"), + SSE42 = dict(interest=7, implies="POPCNT"), + AVX = dict( + interest=8, implies="SSE42", headers="immintrin.h", + implies_detect=False + ), + XOP = dict(interest=9, implies="AVX", headers="x86intrin.h"), + FMA4 = dict(interest=10, implies="AVX", headers="x86intrin.h"), + F16C = dict(interest=11, implies="AVX"), + FMA3 = dict(interest=12, implies="F16C"), + AVX2 = dict(interest=13, implies="F16C"), + AVX512F = dict(interest=20, implies="FMA3 AVX2", implies_detect=False), + AVX512CD = dict(interest=21, implies="AVX512F"), + AVX512_KNL = dict( + interest=40, implies="AVX512CD", group="AVX512ER AVX512PF", + detect="AVX512_KNL", implies_detect=False + ), + AVX512_KNM = dict( + interest=41, implies="AVX512_KNL", + group="AVX5124FMAPS AVX5124VNNIW AVX512VPOPCNTDQ", + detect="AVX512_KNM", implies_detect=False + ), + AVX512_SKX = dict( + interest=42, implies="AVX512CD", group="AVX512VL AVX512BW AVX512DQ", + detect="AVX512_SKX", implies_detect=False + ), + AVX512_CLX = dict( + interest=43, implies="AVX512_SKX", group="AVX512VNNI", + detect="AVX512_CLX" + ), + AVX512_CNL = dict( + interest=44, implies="AVX512_SKX", group="AVX512IFMA AVX512VBMI", + detect="AVX512_CNL", implies_detect=False + ), + AVX512_ICL = dict( + interest=45, implies="AVX512_CLX AVX512_CNL", + group="AVX512VBMI2 AVX512BITALG AVX512VPOPCNTDQ", + detect="AVX512_ICL", implies_detect=False + ), + # IBM/Power + ## Power7/ISA 2.06 + VSX = dict(interest=1, headers="altivec.h"), + ## Power8/ISA 2.07 + VSX2 = dict(interest=2, implies="VSX", implies_detect=False), + ## Power9/ISA 3.00 + VSX3 = dict(interest=3, implies="VSX2", implies_detect=False), + # ARM + NEON = dict(interest=1, headers="arm_neon.h"), + NEON_FP16 = dict(interest=2, implies="NEON"), + ## FMA + NEON_VFPV4 = dict(interest=3, implies="NEON_FP16"), + ## Advanced SIMD + ASIMD = dict(interest=4, implies="NEON_FP16 NEON_VFPV4", implies_detect=False), + ## ARMv8.2 half-precision & vector arithm + ASIMDHP = dict(interest=5, implies="ASIMD"), + ## ARMv8.2 dot product + ASIMDDP = dict(interest=6, implies="ASIMD"), + ## ARMv8.2 Single & half-precision Multiply + ASIMDFHM = dict(interest=7, implies="ASIMDHP"), + ) + def conf_features_partial(self): + """Return a dictionary of supported CPU features by the platform, + and accumulate the rest of undefined options in `conf_features`, + the returned dict has same rules and notes in + class attribute `conf_features`, also its override + any options that been set in 'conf_features'. + """ + if self.cc_noopt: + # optimization is disabled + return {} + + on_x86 = self.cc_on_x86 or self.cc_on_x64 + is_unix = self.cc_is_gcc or self.cc_is_clang + + if on_x86 and is_unix: return dict( + SSE = dict(flags="-msse"), + SSE2 = dict(flags="-msse2"), + SSE3 = dict(flags="-msse3"), + SSSE3 = dict(flags="-mssse3"), + SSE41 = dict(flags="-msse4.1"), + POPCNT = dict(flags="-mpopcnt"), + SSE42 = dict(flags="-msse4.2"), + AVX = dict(flags="-mavx"), + F16C = dict(flags="-mf16c"), + XOP = dict(flags="-mxop"), + FMA4 = dict(flags="-mfma4"), + FMA3 = dict(flags="-mfma"), + AVX2 = dict(flags="-mavx2"), + AVX512F = dict(flags="-mavx512f"), + AVX512CD = dict(flags="-mavx512cd"), + AVX512_KNL = dict(flags="-mavx512er -mavx512pf"), + AVX512_KNM = dict( + flags="-mavx5124fmaps -mavx5124vnniw -mavx512vpopcntdq" + ), + AVX512_SKX = dict(flags="-mavx512vl -mavx512bw -mavx512dq"), + AVX512_CLX = dict(flags="-mavx512vnni"), + AVX512_CNL = dict(flags="-mavx512ifma -mavx512vbmi"), + AVX512_ICL = dict( + flags="-mavx512vbmi2 -mavx512bitalg -mavx512vpopcntdq" + ) + ) + if on_x86 and self.cc_is_icc: return dict( + SSE = dict(flags="-msse"), + SSE2 = dict(flags="-msse2"), + SSE3 = dict(flags="-msse3"), + SSSE3 = dict(flags="-mssse3"), + SSE41 = dict(flags="-msse4.1"), + POPCNT = {}, + SSE42 = dict(flags="-msse4.2"), + AVX = dict(flags="-mavx"), + F16C = {}, + XOP = dict(disable="Intel Compiler doesn't support it"), + FMA4 = dict(disable="Intel Compiler doesn't support it"), + # Intel Compiler doesn't support AVX2 or FMA3 independently + FMA3 = dict( + implies="F16C AVX2", flags="-march=core-avx2" + ), + AVX2 = dict(implies="FMA3", flags="-march=core-avx2"), + # Intel Compiler doesn't support AVX512F or AVX512CD independently + AVX512F = dict( + implies="AVX2 AVX512CD", flags="-march=common-avx512" + ), + AVX512CD = dict( + implies="AVX2 AVX512F", flags="-march=common-avx512" + ), + AVX512_KNL = dict(flags="-xKNL"), + AVX512_KNM = dict(flags="-xKNM"), + AVX512_SKX = dict(flags="-xSKYLAKE-AVX512"), + AVX512_CLX = dict(flags="-xCASCADELAKE"), + AVX512_CNL = dict(flags="-xCANNONLAKE"), + AVX512_ICL = dict(flags="-xICELAKE-CLIENT"), + ) + if on_x86 and self.cc_is_iccw: return dict( + SSE = dict(flags="/arch:SSE"), + SSE2 = dict(flags="/arch:SSE2"), + SSE3 = dict(flags="/arch:SSE3"), + SSSE3 = dict(flags="/arch:SSSE3"), + SSE41 = dict(flags="/arch:SSE4.1"), + POPCNT = {}, + SSE42 = dict(flags="/arch:SSE4.2"), + AVX = dict(flags="/arch:AVX"), + F16C = {}, + XOP = dict(disable="Intel Compiler doesn't support it"), + FMA4 = dict(disable="Intel Compiler doesn't support it"), + # Intel Compiler doesn't support FMA3 or AVX2 independently + FMA3 = dict( + implies="F16C AVX2", flags="/arch:CORE-AVX2" + ), + AVX2 = dict( + implies="FMA3", flags="/arch:CORE-AVX2" + ), + # Intel Compiler doesn't support AVX512F or AVX512CD independently + AVX512F = dict( + implies="AVX2 AVX512CD", flags="/Qx:COMMON-AVX512" + ), + AVX512CD = dict( + implies="AVX2 AVX512F", flags="/Qx:COMMON-AVX512" + ), + AVX512_KNL = dict(flags="/Qx:KNL"), + AVX512_KNM = dict(flags="/Qx:KNM"), + AVX512_SKX = dict(flags="/Qx:SKYLAKE-AVX512"), + AVX512_CLX = dict(flags="/Qx:CASCADELAKE"), + AVX512_CNL = dict(flags="/Qx:CANNONLAKE"), + AVX512_ICL = dict(flags="/Qx:ICELAKE-CLIENT") + ) + if on_x86 and self.cc_is_msvc: return dict( + SSE = dict(flags="/arch:SSE"), + SSE2 = dict(flags="/arch:SSE2"), + SSE3 = {}, + SSSE3 = {}, + SSE41 = {}, + POPCNT = dict(headers="nmmintrin.h"), + SSE42 = {}, + AVX = dict(flags="/arch:AVX"), + F16C = {}, + XOP = dict(headers="ammintrin.h"), + FMA4 = dict(headers="ammintrin.h"), + # MSVC doesn't support FMA3 or AVX2 independently + FMA3 = dict( + implies="F16C AVX2", flags="/arch:AVX2" + ), + AVX2 = dict( + implies="F16C FMA3", flags="/arch:AVX2" + ), + # MSVC doesn't support AVX512F or AVX512CD independently, + # always generate instructions belong to (VL/VW/DQ) + AVX512F = dict( + implies="AVX2 AVX512CD AVX512_SKX", flags="/arch:AVX512" + ), + AVX512CD = dict( + implies="AVX512F AVX512_SKX", flags="/arch:AVX512" + ), + AVX512_KNL = dict( + disable="MSVC compiler doesn't support it" + ), + AVX512_KNM = dict( + disable="MSVC compiler doesn't support it" + ), + AVX512_SKX = dict(flags="/arch:AVX512"), + AVX512_CLX = {}, + AVX512_CNL = {}, + AVX512_ICL = {} + ) + + on_power = self.cc_on_ppc64le or self.cc_on_ppc64 + if on_power: + partial = dict( + VSX = dict( + implies=("VSX2" if self.cc_on_ppc64le else ""), + flags="-mvsx" + ), + VSX2 = dict( + flags="-mcpu=power8", implies_detect=False + ), + VSX3 = dict( + flags="-mcpu=power9 -mtune=power9", implies_detect=False + ) + ) + if self.cc_is_clang: + partial["VSX"]["flags"] = "-maltivec -mvsx" + partial["VSX2"]["flags"] = "-mpower8-vector" + partial["VSX3"]["flags"] = "-mpower9-vector" + + return partial + + if self.cc_on_aarch64 and is_unix: return dict( + NEON = dict( + implies="NEON_FP16 NEON_VFPV4 ASIMD", autovec=True + ), + NEON_FP16 = dict( + implies="NEON NEON_VFPV4 ASIMD", autovec=True + ), + NEON_VFPV4 = dict( + implies="NEON NEON_FP16 ASIMD", autovec=True + ), + ASIMD = dict( + implies="NEON NEON_FP16 NEON_VFPV4", autovec=True + ), + ASIMDHP = dict( + flags="-march=armv8.2-a+fp16" + ), + ASIMDDP = dict( + flags="-march=armv8.2-a+dotprod" + ), + ASIMDFHM = dict( + flags="-march=armv8.2-a+fp16fml" + ), + ) + if self.cc_on_armhf and is_unix: return dict( + NEON = dict( + flags="-mfpu=neon" + ), + NEON_FP16 = dict( + flags="-mfpu=neon-fp16 -mfp16-format=ieee" + ), + NEON_VFPV4 = dict( + flags="-mfpu=neon-vfpv4", + ), + ASIMD = dict( + flags="-mfpu=neon-fp-armv8 -march=armv8-a+simd", + ), + ASIMDHP = dict( + flags="-march=armv8.2-a+fp16" + ), + ASIMDDP = dict( + flags="-march=armv8.2-a+dotprod", + ), + ASIMDFHM = dict( + flags="-march=armv8.2-a+fp16fml" + ) + ) + # TODO: ARM MSVC + return {} + + def __init__(self): + if self.conf_tmp_path is None: + import tempfile, shutil + tmp = tempfile.mkdtemp() + def rm_temp(): + try: + shutil.rmtree(tmp) + except IOError: + pass + atexit.register(rm_temp) + self.conf_tmp_path = tmp + + if self.conf_cache_factors is None: + self.conf_cache_factors = [ + os.path.getmtime(__file__), + self.conf_nocache + ] + +class _Distutils: + """A helper class that provides a collection of fundamental methods + implemented in a top of Python and NumPy Distutils. + + The idea behind this class is to gather all methods that it may + need to override in case of reuse 'CCompilerOpt' in environment + different than of what NumPy has. + + Parameters + ---------- + ccompiler : `CCompiler` + The generate instance that returned from `distutils.ccompiler.new_compiler()`. + """ + def __init__(self, ccompiler): + self._ccompiler = ccompiler + + def dist_compile(self, sources, flags, **kwargs): + """Wrap CCompiler.compile()""" + assert(isinstance(sources, list)) + assert(isinstance(flags, list)) + flags = kwargs.pop("extra_postargs", []) + flags + return self._ccompiler.compile( + sources, extra_postargs=flags, **kwargs + ) + + def dist_test(self, source, flags): + """Return True if 'CCompiler.compile()' able to compile + a source file with certain flags. + """ + assert(isinstance(source, str)) + from distutils.errors import CompileError + cc = self._ccompiler; + bk_spawn = getattr(cc, 'spawn', None) + if bk_spawn: + cc_type = getattr(self._ccompiler, "compiler_type", "") + if cc_type in ("msvc",): + setattr(cc, 'spawn', self._dist_test_spawn_paths) + else: + setattr(cc, 'spawn', self._dist_test_spawn) + test = False + try: + self.dist_compile( + [source], flags, output_dir=self.conf_tmp_path + ) + test = True + except CompileError as e: + self.dist_log(str(e), stderr=True) + if bk_spawn: + setattr(cc, 'spawn', bk_spawn) + return test + + def dist_info(self): + """Return a string containing all environment information, required + by the abstract class '_CCompiler' to discovering the platform + environment, also used as a cache factor in order to detect + any changes from outside. + """ + if hasattr(self, "_dist_info"): + return self._dist_info + # play it safe + cc_info = "" + compiler = getattr(self._ccompiler, "compiler", None) + if compiler is not None: + if isinstance(compiler, str): + cc_info += compiler + elif hasattr(compiler, "__iter__"): + cc_info += ' '.join(compiler) + # in case if 'compiler' attribute doesn't provide anything + cc_type = getattr(self._ccompiler, "compiler_type", "") + if cc_type in ("intelem", "intelemw", "mingw64"): + cc_info += "x86_64" + elif cc_type in ("intel", "intelw", "intele"): + cc_info += "x86" + elif cc_type in ("msvc", "mingw32"): + import platform + if platform.architecture()[0] == "32bit": + cc_info += "x86" + else: + cc_info += "x86_64" + else: + # the last hope, too bad for cross-compiling + import platform + cc_info += platform.machine() + + cc_info += cc_type + cflags = os.environ.get("CFLAGS", "") + if cflags not in cc_info: + cc_info += cflags + + self._dist_info = cc_info + return cc_info + + @staticmethod + def dist_error(*args): + """Raise a compiler error""" + from distutils.errors import CompileError + raise CompileError(_Distutils._dist_str(*args)) + + @staticmethod + def dist_fatal(*args): + """Raise a distutils error""" + from distutils.errors import DistutilsError + raise DistutilsError(_Distutils._dist_str(*args)) + + @staticmethod + def dist_log(*args, stderr=False): + """Print a console message""" + from numpy.distutils import log + out = _Distutils._dist_str(*args) + if stderr: + log.warn(out) + else: + log.info(out) + + @staticmethod + def dist_load_module(name, path): + """Load a module from file, required by the abstract class '_Cache'.""" + from numpy.compat import npy_load_module + try: + return npy_load_module(name, path) + except Exception as e: + _Distutils.dist_log(e, stderr=True) + return None + + @staticmethod + def _dist_str(*args): + """Return a string to print by log and errors.""" + def to_str(arg): + if not isinstance(arg, str) and hasattr(arg, '__iter__'): + ret = [] + for a in arg: + ret.append(to_str(a)) + return '('+ ' '.join(ret) + ')' + return str(arg) + + stack = inspect.stack()[2] + start = "CCompilerOpt.%s[%d] : " % (stack.function, stack.lineno) + out = ' '.join([ + to_str(a) + for a in (*args,) + ]) + return start + out + + def _dist_test_spawn_paths(self, cmd, display=None): + """ + Fix msvc SDK ENV path same as distutils do + without it we get c1: fatal error C1356: unable to find mspdbcore.dll + """ + if not hasattr(self._ccompiler, "_paths"): + self._dist_test_spawn(cmd) + return + old_path = os.getenv("path") + try: + os.environ["path"] = self._ccompiler._paths + self._dist_test_spawn(cmd) + finally: + os.environ["path"] = old_path + + _dist_warn_regex = re.compile( + # intel and msvc compilers don't raise + # fatal errors when flags are wrong or unsupported + ".*(" + "warning D9002|" # msvc, it should be work with any language. + "invalid argument for option" # intel + ").*" + ) + @staticmethod + def _dist_test_spawn(cmd, display=None): + from distutils.errors import CompileError + try: + o = subprocess.check_output(cmd, stderr=subprocess.STDOUT, + universal_newlines=True) + if o and re.match(_Distutils._dist_warn_regex, o): + _Distutils.dist_error( + "Flags in command", cmd ,"aren't supported by the compiler" + ", output -> \n%s" % o + ) + except subprocess.CalledProcessError as exc: + o = exc.output + s = exc.returncode + except OSError: + o = b'' + s = 127 + else: + return None + _Distutils.dist_error( + "Command", cmd, "failed with exit status %d output -> \n%s" % ( + s, o + )) + +_share_cache = {} +class _Cache: + """An abstract class handles caching functionality, provides two + levels of caching, in-memory by share instances attributes among + each other and by store attributes into files. + + **Note**: + any attributes that start with ``_`` or ``conf_`` will be ignored. + + Parameters + ---------- + cache_path: str or None + The path of cache file, if None then cache in file will disabled. + + *factors: + The caching factors that need to utilize next to `conf_cache_factors`. + + Attributes + ---------- + cache_private: set + Hold the attributes that need be skipped from "in-memory cache". + + cache_infile: bool + Utilized during initializing this class, to determine if the cache was able + to loaded from the specified cache path in 'cache_path'. + """ + + # skip attributes from cache + _cache_ignore = re.compile("^(_|conf_)") + + def __init__(self, cache_path=None, *factors): + self.cache_me = {} + self.cache_private = set() + self.cache_infile = False + + if self.conf_nocache: + self.dist_log("cache is disabled by `Config`") + return + + chash = self.cache_hash(*factors, *self.conf_cache_factors) + if cache_path: + if os.path.exists(cache_path): + self.dist_log("load cache from file ->", cache_path) + cache_mod = self.dist_load_module("cache", cache_path) + if not cache_mod: + self.dist_log( + "unable to load the cache file as a module", + stderr=True + ) + elif not hasattr(cache_mod, "hash") or \ + not hasattr(cache_mod, "data"): + self.dist_log("invalid cache file", stderr=True) + elif chash == cache_mod.hash: + self.dist_log("hit the file cache") + for attr, val in cache_mod.data.items(): + setattr(self, attr, val) + self.cache_infile = True + else: + self.dist_log("miss the file cache") + + atexit.register(self._cache_write, cache_path, chash) + + if not self.cache_infile: + other_cache = _share_cache.get(chash) + if other_cache: + self.dist_log("hit the memory cache") + for attr, val in other_cache.__dict__.items(): + if attr in other_cache.cache_private or \ + re.match(self._cache_ignore, attr): + continue + setattr(self, attr, val) + + _share_cache[chash] = self + + def __del__(self): + # TODO: remove the cache form share on del + pass + + def _cache_write(self, cache_path, cache_hash): + # TODO: don't write if the cache doesn't change + self.dist_log("write cache to path ->", cache_path) + for attr in list(self.__dict__.keys()): + if re.match(self._cache_ignore, attr): + self.__dict__.pop(attr) + + d = os.path.dirname(cache_path) + if not os.path.exists(d): + os.makedirs(d) + + repr_dict = pprint.pformat(self.__dict__, compact=True) + with open(cache_path, "w") as f: + f.write(textwrap.dedent("""\ + # AUTOGENERATED DON'T EDIT + # Please make changes to the code generator \ + (distutils/ccompiler_opt.py) + hash = {} + data = \\ + """).format(cache_hash)) + f.write(repr_dict) + + def cache_hash(self, *factors): + # is there a built-in non-crypto hash? + # sdbm + chash = 0 + for f in factors: + for char in str(f): + chash = ord(char) + (chash << 6) + (chash << 16) - chash + chash &= 0xFFFFFFFF + return chash + + @staticmethod + def me(cb): + """ + A static method that can be treated as a decorator to + dynamically cache certain methods. + """ + def cache_wrap_me(self, *args, **kwargs): + # good for normal args + cache_key = str(( + cb.__name__, *args, *kwargs.keys(), *kwargs.values() + )) + if cache_key in self.cache_me: + return self.cache_me[cache_key] + ccb = cb(self, *args, **kwargs) + self.cache_me[cache_key] = ccb + return ccb + return cache_wrap_me + +class _CCompiler(object): + """A helper class for `CCompilerOpt` containing all utilities that + related to the fundamental compiler's functions. + + Attributes + ---------- + cc_on_x86 : bool + True when the target architecture is 32-bit x86 + cc_on_x64 : bool + True when the target architecture is 64-bit x86 + cc_on_ppc64 : bool + True when the target architecture is 64-bit big-endian PowerPC + cc_on_armhf : bool + True when the target architecture is 32-bit ARMv7+ + cc_on_aarch64 : bool + True when the target architecture is 64-bit Armv8-a+ + cc_on_noarch : bool + True when the target architecture is unknown or not supported + cc_is_gcc : bool + True if the compiler is GNU or + if the compiler is unknown + cc_is_clang : bool + True if the compiler is Clang + cc_is_icc : bool + True if the compiler is Intel compiler (unix like) + cc_is_iccw : bool + True if the compiler is Intel compiler (msvc like) + cc_is_nocc : bool + True if the compiler isn't supported directly, + Note: that cause a fail-back to gcc + cc_has_debug : bool + True if the compiler has debug flags + cc_has_native : bool + True if the compiler has native flags + cc_noopt : bool + True if the compiler has definition 'DISABLE_OPT*', + or 'cc_on_noarch' is True + cc_march : str + The target architecture name, or "unknown" if + the architecture isn't supported + cc_name : str + The compiler name, or "unknown" if the compiler isn't supported + cc_flags : dict + Dictionary containing the initialized flags of `_Config.conf_cc_flags` + """ + def __init__(self): + if hasattr(self, "cc_is_cached"): + return + to_detect = ( + # attr regex + ( + ("cc_on_x64", "^(x|x86_|amd)64"), + ("cc_on_x86", "^(x86|i386|i686)"), + ("cc_on_ppc64le", "^(powerpc|ppc)64(el|le)"), + ("cc_on_ppc64", "^(powerpc|ppc)64"), + ("cc_on_armhf", "^arm"), + ("cc_on_aarch64", "^aarch64"), + # priority is given to first of string + # if it fail we search in the rest, due + # to append platform.machine() at the end, + # check method 'dist_info()' for more clarification. + ("cc_on_x64", ".*(x|x86_|amd)64.*"), + ("cc_on_x86", ".*(x86|i386|i686).*"), + ("cc_on_ppc64le", ".*(powerpc|ppc)64(el|le).*"), + ("cc_on_ppc64", ".*(powerpc|ppc)64.*"), + ("cc_on_armhf", ".*arm.*"), + ("cc_on_aarch64", ".*aarch64.*"), + # undefined platform + ("cc_on_noarch", ""), + ), + ( + ("cc_is_gcc", r".*(gcc|gnu\-g).*"), + ("cc_is_clang", ".*clang.*"), + ("cc_is_iccw", ".*(intelw|intelemw|iccw).*"), # intel msvc like + ("cc_is_icc", ".*(intel|icc).*"), # intel unix like + ("cc_is_msvc", ".*msvc.*"), + ("cc_is_nocc", ""), + ), + (("cc_has_debug", ".*(O0|Od|ggdb|coverage|debug:full).*"),), + (("cc_has_native", ".*(-march=native|-xHost|/QxHost).*"),), + # in case if the class run with -DNPY_DISABLE_OPTIMIZATION + (("cc_noopt", ".*DISABLE_OPT.*"),), + ) + for section in to_detect: + for attr, rgex in section: + setattr(self, attr, False) + + dist_info = self.dist_info() + for section in to_detect: + for attr, rgex in section: + if rgex and not re.match(rgex, dist_info, re.IGNORECASE): + continue + setattr(self, attr, True) + break + + if self.cc_on_noarch: + self.dist_log( + "unable to detect CPU arch via compiler info, " + "optimization is disabled \ninfo << %s >> " % dist_info, + stderr=True + ) + self.cc_noopt = True + + if self.conf_noopt: + self.dist_log("Optimization is disabled by the Config", stderr=True) + self.cc_noopt = True + + if self.cc_is_nocc: + """ + mingw can be treated as a gcc, and also xlc even if it based on clang, + but still has the same gcc optimization flags. + """ + self.dist_log( + "unable to detect compiler name via info <<\n%s\n>> " + "treating it as a gcc" % dist_info, + stderr=True + ) + self.cc_is_gcc = True + + self.cc_march = "unknown" + for arch in ("x86", "x64", "ppc64", "ppc64le", "armhf", "aarch64"): + if getattr(self, "cc_on_" + arch): + self.cc_march = arch + break + + self.cc_name = "unknown" + for name in ("gcc", "clang", "iccw", "icc", "msvc"): + if getattr(self, "cc_is_" + name): + self.cc_name = name + break + + self.cc_flags = {} + compiler_flags = self.conf_cc_flags.get(self.cc_name) + if compiler_flags is None: + self.dist_fatal( + "undefined flag for compiler '%s', " + "leave an empty dict instead" % self.cc_name + ) + for name, flags in compiler_flags.items(): + self.cc_flags[name] = nflags = [] + if flags: + assert(isinstance(flags, str)) + flags = flags.split() + for f in flags: + if self.cc_test_flags([f]): + nflags.append(f) + + self.cc_is_cached = True + + @_Cache.me + def cc_test_flags(self, flags): + """ + Returns True if the compiler supports 'flags'. + """ + assert(isinstance(flags, list)) + self.dist_log("testing flags", flags) + test_path = os.path.join(self.conf_check_path, "test_flags.c") + test = self.dist_test(test_path, flags) + if not test: + self.dist_log("testing failed", stderr=True) + return test + + def cc_normalize_flags(self, flags): + """ + Remove the conflicts that caused due gathering implied features flags. + + Parameters + ---------- + 'flags' list, compiler flags + flags should be sorted from the lowest to the highest interest. + + Returns + ------- + list, filtered from any conflicts. + + Examples + -------- + >>> self.cc_normalize_flags(['-march=armv8.2-a+fp16', '-march=armv8.2-a+dotprod']) + ['armv8.2-a+fp16+dotprod'] + + >>> self.cc_normalize_flags( + ['-msse', '-msse2', '-msse3', '-mssse3', '-msse4.1', '-msse4.2', '-mavx', '-march=core-avx2'] + ) + ['-march=core-avx2'] + """ + assert(isinstance(flags, list)) + if self.cc_is_gcc or self.cc_is_clang or self.cc_is_icc: + return self._cc_normalize_unix(flags) + + if self.cc_is_msvc or self.cc_is_iccw: + return self._cc_normalize_win(flags) + return flags + + _cc_normalize_unix_mrgx = re.compile( + # 1- to check the highest of + r"^(-mcpu=|-march=|-x[A-Z0-9\-])" + ) + _cc_normalize_unix_frgx = re.compile( + # 2- to remove any flags starts with + # -march, -mcpu, -x(INTEL) and '-m' without '=' + r"^(?!(-mcpu=|-march=|-x[A-Z0-9\-]))(?!-m[a-z0-9\-\.]*.$)" + ) + _cc_normalize_unix_krgx = re.compile( + # 3- keep only the highest of + r"^(-mfpu|-mtune)" + ) + _cc_normalize_arch_ver = re.compile( + r"[0-9.]" + ) + def _cc_normalize_unix(self, flags): + def ver_flags(f): + # arch ver subflag + # -march=armv8.2-a+fp16fml + tokens = f.split('+') + ver = float('0' + ''.join( + re.findall(self._cc_normalize_arch_ver, tokens[0]) + )) + return ver, tokens[0], tokens[1:] + + if len(flags) <= 1: + return flags + # get the highest matched flag + for i, cur_flag in enumerate(reversed(flags)): + if not re.match(self._cc_normalize_unix_mrgx, cur_flag): + continue + lower_flags = flags[:-(i+1)] + upper_flags = flags[-i:] + filterd = list(filter( + self._cc_normalize_unix_frgx.search, lower_flags + )) + # gather subflags + ver, arch, subflags = ver_flags(cur_flag) + if ver > 0 and len(subflags) > 0: + for xflag in lower_flags: + xver, _, xsubflags = ver_flags(xflag) + if ver == xver: + subflags = xsubflags + subflags + cur_flag = arch + '+' + '+'.join(subflags) + + flags = filterd + [cur_flag] + if i > 0: + flags += upper_flags + break + + # to remove overridable flags + final_flags = [] + matched = set() + for f in reversed(flags): + match = re.match(self._cc_normalize_unix_krgx, f) + if not match: + pass + elif match[0] in matched: + continue + else: + matched.add(match[0]) + final_flags.insert(0, f) + return final_flags + + _cc_normalize_win_frgx = re.compile( + r"^(?!(/arch\:|/Qx\:))" + ) + _cc_normalize_win_mrgx = re.compile( + r"^(/arch|/Qx:)" + ) + def _cc_normalize_win(self, flags): + for i, f in enumerate(reversed(flags)): + if not re.match(self._cc_normalize_win_mrgx, f): + continue + i += 1 + return list(filter( + self._cc_normalize_win_frgx.search, flags[:-i] + )) + flags[-i:] + return flags + +class _Feature: + """A helper class for `CCompilerOpt` that managing CPU features. + + Attributes + ---------- + feature_supported : dict + Dictionary containing all CPU features that supported + by the platform, according to the specified values in attribute + `_Config.conf_features` and `_Config.conf_features_partial()` + + feature_min : set + The minimum support of CPU features, according to + the specified values in attribute `_Config.conf_min_features`. + """ + def __init__(self): + if hasattr(self, "feature_is_cached"): + return + self.feature_supported = pfeatures = self.conf_features_partial() + for feature_name in list(pfeatures.keys()): + feature = pfeatures[feature_name] + cfeature = self.conf_features[feature_name] + feature.update({ + k:v for k,v in cfeature.items() if k not in feature + }) + disabled = feature.get("disable") + if disabled is not None: + pfeatures.pop(feature_name) + self.dist_log( + "feature '%s' is disabled," % feature_name, + disabled, stderr=True + ) + continue + # list is used internally for these options + for option in ( + "implies", "group", "detect", "headers", "flags" + ) : + oval = feature.get(option) + if isinstance(oval, str): + feature[option] = oval.split() + + self.feature_min = set() + min_f = self.conf_min_features.get(self.cc_march, "") + for F in min_f.upper().split(): + if F in self.feature_supported: + self.feature_min.add(F) + + self.feature_is_cached = True + + def feature_names(self, names=None, force_flags=None): + """ + Returns a set of CPU feature names that supported by platform and the **C** compiler. + + Parameters + ---------- + 'names': sequence or None, optional + Specify certain CPU features to test it against the **C** compiler. + if None(default), it will test all current supported features. + **Note**: feature names must be in upper-case. + + 'force_flags': list or None, optional + If None(default), default compiler flags for every CPU feature will be used + during the test. + """ + assert( + names is None or ( + not isinstance(names, str) and + hasattr(names, "__iter__") + ) + ) + assert(force_flags is None or isinstance(force_flags, list)) + if names is None: + names = self.feature_supported.keys() + supported_names = set() + for f in names: + if self.feature_is_supported(f, force_flags=force_flags): + supported_names.add(f) + return supported_names + + def feature_is_exist(self, name): + """ + Returns True if a certain feature is exist and covered within + `_Config.conf_features`. + + Parameters + ---------- + 'name': str + feature name in uppercase. + """ + assert(name.isupper()) + return name in self.conf_features + + def feature_sorted(self, names, reverse=False): + """ + Sort a list of CPU features ordered by the lowest interest. + + Parameters + ---------- + 'names': sequence + sequence of supported feature names in uppercase. + 'reverse': bool, optional + If true, the sorted features is reversed. (highest interest) + + Returns + ------- + list, sorted CPU features + """ + def sort_cb(k): + if isinstance(k, str): + return self.feature_supported[k]["interest"] + # multiple features + rank = max([self.feature_supported[f]["interest"] for f in k]) + # FIXME: that's not a safe way to increase the rank for + # multi targets + rank += len(k) -1 + return rank + return sorted(names, reverse=reverse, key=sort_cb) + + def feature_implies(self, names, keep_origins=False): + """ + Return a set of CPU features that implied by 'names' + + Parameters + ---------- + names: str or sequence of str + CPU feature name(s) in uppercase. + + keep_origins: bool + if False(default) then the returned set will not contain any + features from 'names'. This case happens only when two features + imply each other. + + Examples + -------- + >>> self.feature_implies("SSE3") + {'SSE', 'SSE2'} + >>> self.feature_implies("SSE2") + {'SSE'} + >>> self.feature_implies("SSE2", keep_origins=True) + # 'SSE2' found here since 'SSE' and 'SSE2' imply each other + {'SSE', 'SSE2'} + """ + def get_implies(name, _caller=set()): + implies = set() + d = self.feature_supported[name] + for i in d.get("implies", []): + implies.add(i) + if i in _caller: + # infinity recursive guard since + # features can imply each other + continue + _caller.add(name) + implies = implies.union(get_implies(i, _caller)) + return implies + + if isinstance(names, str): + implies = get_implies(names) + names = [names] + else: + assert(hasattr(names, "__iter__")) + implies = set() + for n in names: + implies = implies.union(get_implies(n)) + if not keep_origins: + implies.difference_update(names) + return implies + + def feature_implies_c(self, names): + """same as feature_implies() but combining 'names'""" + if isinstance(names, str): + names = set((names,)) + else: + names = set(names) + return names.union(self.feature_implies(names)) + + def feature_ahead(self, names): + """ + Return list of features in 'names' after remove any + implied features and keep the origins. + + Parameters + ---------- + 'names': sequence + sequence of CPU feature names in uppercase. + + Returns + ------- + list of CPU features sorted as-is 'names' + + Examples + -------- + >>> self.feature_ahead(["SSE2", "SSE3", "SSE41"]) + ["SSE41"] + # assume AVX2 and FMA3 implies each other and AVX2 + # is the highest interest + >>> self.feature_ahead(["SSE2", "SSE3", "SSE41", "AVX2", "FMA3"]) + ["AVX2"] + # assume AVX2 and FMA3 don't implies each other + >>> self.feature_ahead(["SSE2", "SSE3", "SSE41", "AVX2", "FMA3"]) + ["AVX2", "FMA3"] + """ + assert( + not isinstance(names, str) + and hasattr(names, '__iter__') + ) + implies = self.feature_implies(names, keep_origins=True) + ahead = [n for n in names if n not in implies] + if len(ahead) == 0: + # return the highest interested feature + # if all features imply each other + ahead = self.feature_sorted(names, reverse=True)[:1] + return ahead + + def feature_untied(self, names): + """ + same as 'feature_ahead()' but if both features implied each other + and keep the highest interest. + + Parameters + ---------- + 'names': sequence + sequence of CPU feature names in uppercase. + + Returns + ------- + list of CPU features sorted as-is 'names' + + Examples + -------- + >>> self.feature_untied(["SSE2", "SSE3", "SSE41"]) + ["SSE2", "SSE3", "SSE41"] + # assume AVX2 and FMA3 implies each other + >>> self.feature_untied(["SSE2", "SSE3", "SSE41", "FMA3", "AVX2"]) + ["SSE2", "SSE3", "SSE41", "AVX2"] + """ + assert( + not isinstance(names, str) + and hasattr(names, '__iter__') + ) + final = [] + for n in names: + implies = self.feature_implies(n) + tied = [ + nn for nn in final + if nn in implies and n in self.feature_implies(nn) + ] + if tied: + tied = self.feature_sorted(tied + [n]) + if n not in tied[1:]: + continue + final.remove(tied[:1][0]) + final.append(n) + return final + + def feature_get_til(self, names, keyisfalse): + """ + same as `feature_implies_c()` but stop collecting implied + features when feature's option that provided through + parameter 'keyisfalse' is False, also sorting the returned + features. + """ + def til(tnames): + # sort from highest to lowest interest then cut if "key" is False + tnames = self.feature_implies_c(tnames) + tnames = self.feature_sorted(tnames, reverse=True) + for i, n in enumerate(tnames): + if not self.feature_supported[n].get(keyisfalse, True): + tnames = tnames[:i+1] + break + return tnames + + if isinstance(names, str) or len(names) <= 1: + names = til(names) + # normalize the sort + names.reverse() + return names + + names = self.feature_ahead(names) + names = {t for n in names for t in til(n)} + return self.feature_sorted(names) + + def feature_detect(self, names): + """ + Return a list of CPU features that required to be detected + sorted from the lowest to highest interest. + """ + names = self.feature_get_til(names, "implies_detect") + detect = [] + for n in names: + d = self.feature_supported[n] + detect += d.get("detect", d.get("group", [n])) + return detect + + @_Cache.me + def feature_flags(self, names): + """ + Return a list of CPU features flags sorted from the lowest + to highest interest. + """ + names = self.feature_sorted(self.feature_implies_c(names)) + flags = [] + for n in names: + d = self.feature_supported[n] + f = d.get("flags", []) + if not f or not self.cc_test_flags(f): + continue + flags += f + return self.cc_normalize_flags(flags) + + @_Cache.me + def feature_test(self, name, force_flags=None): + """ + Test a certain CPU feature against the compiler through its own + check file. + + Parameters + ---------- + 'name': str + Supported CPU feature name. + + 'force_flags': list or None, optional + If None(default), the returned flags from `feature_flags()` + will be used. + """ + if force_flags is None: + force_flags = self.feature_flags(name) + + self.dist_log( + "testing feature '%s' with flags (%s)" % ( + name, ' '.join(force_flags) + )) + # Each CPU feature must have C source code contains at + # least one intrinsic or instruction related to this feature. + test_path = os.path.join( + self.conf_check_path, "cpu_%s.c" % name.lower() + ) + if not os.path.exists(test_path): + self.dist_fatal("feature test file is not exist", path) + + test = self.dist_test(test_path, force_flags + self.cc_flags["werror"]) + if not test: + self.dist_log("testing failed", stderr=True) + return test + + @_Cache.me + def feature_is_supported(self, name, force_flags=None): + """ + Check if a certain CPU feature is supported by the platform and compiler. + + Parameters + ---------- + 'name': str + CPU feature name in uppercase. + + 'force_flags': list or None, optional + If None(default), default compiler flags for every CPU feature will be used + during test. + """ + assert(name.isupper()) + assert(force_flags is None or isinstance(force_flags, list)) + + supported = name in self.feature_supported + if supported: + for impl in self.feature_implies(name): + if not self.feature_test(impl, force_flags): + return False + if not self.feature_test(name, force_flags): + return False + return supported + + @_Cache.me + def feature_can_autovec(self, name): + """ + check if the feature can be auto-vectorized by the compiler + """ + assert(isinstance(name, str)) + d = self.feature_supported[name] + can = d.get("autovec", None) + if can is None: + valid_flags = [ + self.cc_test_flags([f]) for f in d.get("flags", []) + ] + can = valid_flags and any(valid_flags) + return can + + def feature_c_preprocessor(self, feature_name, tabs=0): + """ + Generate C preprocessor definitions and include headers of a CPU feature. + + Parameters + ---------- + 'feature_name': str + CPU feature name in uppercase. + 'tabs': int + if > 0, align the generated strings to the right depend on number of tabs. + + Returns + ------- + str, generated C preprocessor + + Examples + -------- + >>> self.feature_c_preprocessor("SSE3") + /** SSE3 **/ + #define NPY_HAVE_SSE3 1 + #include <pmmintrin.h> + """ + assert(feature_name.isupper()) + feature = self.feature_supported.get(feature_name) + assert(feature is not None) + + prepr = [ + "/** %s **/" % feature_name, + "#define %sHAVE_%s 1" % (self.conf_c_prefix, feature_name) + ] + prepr += [ + "#include <%s>" % h for h in feature.get("headers", []) + ] + group = feature.get("group", []) + for f in group: + # Guard features in case of duplicate definitions + prepr += [ + "#ifndef %sHAVE_%s" % (self.conf_c_prefix, f), + "\t#define %sHAVE_%s 1" % (self.conf_c_prefix, f), + "#endif", + ] + if tabs > 0: + prepr = [('\t'*tabs) + l for l in prepr] + return '\n'.join(prepr) + +class _Parse: + """A helper class that parsing main arguments of `CCompilerOpt`, + also parsing configuration statements in dispatch-able sources. + + Parameters + ---------- + cpu_baseline: str or None + minimal set of required CPU features or special options. + + cpu_dispatch: str or None + dispatched set of additional CPU features or special options. + + Special options can be: + - **MIN**: Enables the minimum CPU features that utilized via `_Config.conf_min_features` + - **MAX**: Enables all supported CPU features by the Compiler and platform. + - **NATIVE**: Enables all CPU features that supported by the current machine. + - **NONE**: Enables nothing + - **Operand +/-**: remove or add features, useful with options **MAX**, **MIN** and **NATIVE**. + NOTE: operand + is only added for nominal reason. + + NOTES: + - Case-insensitive among all CPU features and special options. + - Comma or space can be used as a separator. + - If the CPU feature is not supported by the user platform or compiler, + it will be skipped rather than raising a fatal error. + - Any specified CPU features to 'cpu_dispatch' will be skipped if its part of CPU baseline features + - 'cpu_baseline' force enables implied features. + + Attributes + ---------- + parse_baseline_names : list + Final CPU baseline's feature names(sorted from low to high) + parse_baseline_flags : list + Compiler flags of baseline features + parse_dispatch_names : list + Final CPU dispatch-able feature names(sorted from low to high) + parse_target_groups : dict + Dictionary containing initialized target groups that configured + through class attribute `conf_target_groups`. + + The key is represent the group name and value is a tuple + contains three items : + - bool, True if group has the 'baseline' option. + - list, list of CPU features. + - list, list of extra compiler flags. + + """ + def __init__(self, cpu_baseline, cpu_dispatch): + self._parse_policies = dict( + # POLICY NAME, (HAVE, NOT HAVE, [DEB]) + KEEP_BASELINE = ( + None, self._parse_policy_not_keepbase, + [] + ), + KEEP_SORT = ( + self._parse_policy_keepsort, + self._parse_policy_not_keepsort, + [] + ), + MAXOPT = ( + self._parse_policy_maxopt, None, + [] + ), + WERROR = ( + self._parse_policy_werror, None, + [] + ), + AUTOVEC = ( + self._parse_policy_autovec, None, + ["MAXOPT"] + ) + ) + if hasattr(self, "parse_is_cached"): + return + + self.parse_baseline_names = [] + self.parse_baseline_flags = [] + self.parse_dispatch_names = [] + self.parse_target_groups = {} + + if self.cc_noopt: + # skip parsing baseline and dispatch args and keep parsing target groups + cpu_baseline = cpu_dispatch = None + + self.dist_log("check requested baseline") + if cpu_baseline is not None: + cpu_baseline = self._parse_arg_features("cpu_baseline", cpu_baseline) + baseline_names = self.feature_names(cpu_baseline) + self.parse_baseline_flags = self.feature_flags(baseline_names) + self.parse_baseline_names = self.feature_sorted( + self.feature_implies_c(baseline_names) + ) + + self.dist_log("check requested dispatch-able features") + if cpu_dispatch is not None: + cpu_dispatch_ = self._parse_arg_features("cpu_dispatch", cpu_dispatch) + cpu_dispatch = { + f for f in cpu_dispatch_ + if f not in self.parse_baseline_names + } + conflict_baseline = cpu_dispatch_.difference(cpu_dispatch) + self.parse_dispatch_names = self.feature_sorted( + self.feature_names(cpu_dispatch) + ) + if len(conflict_baseline) > 0: + self.dist_log( + "skip features", conflict_baseline, "since its part of baseline" + ) + + self.dist_log("initialize targets groups") + for group_name, tokens in self.conf_target_groups.items(): + self.dist_log("parse target group", group_name) + GROUP_NAME = group_name.upper() + if not tokens or not tokens.strip(): + # allow empty groups, useful in case if there's a need + # to disable certain group since '_parse_target_tokens()' + # requires at least one valid target + self.parse_target_groups[GROUP_NAME] = ( + False, [], [] + ) + continue + has_baseline, features, extra_flags = \ + self._parse_target_tokens(tokens) + self.parse_target_groups[GROUP_NAME] = ( + has_baseline, features, extra_flags + ) + + self.parse_is_cached = True + + def parse_targets(self, source): + """ + Fetch and parse configuration statements that required for + defining the targeted CPU features, statements should be declared + in the top of source in between **C** comment and start + with a special mark **@targets**. + + Configuration statements are sort of keywords representing + CPU features names, group of statements and policies, combined + together to determine the required optimization. + + Parameters + ---------- + source: str + the path of **C** source file. + + Returns + ------- + - bool, True if group has the 'baseline' option + - list, list of CPU features + - list, list of extra compiler flags + """ + self.dist_log("looking for '@targets' inside -> ", source) + # get lines between /*@targets and */ + with open(source) as fd: + tokens = "" + max_to_reach = 1000 # good enough, isn't? + start_with = "@targets" + start_pos = -1 + end_with = "*/" + end_pos = -1 + for current_line, line in enumerate(fd): + if current_line == max_to_reach: + self.dist_fatal("reached the max of lines") + break + if start_pos == -1: + start_pos = line.find(start_with) + if start_pos == -1: + continue + start_pos += len(start_with) + tokens += line + end_pos = line.find(end_with) + if end_pos != -1: + end_pos += len(tokens) - len(line) + break + + if start_pos == -1: + self.dist_fatal("expected to find '%s' within a C comment" % start_with) + if end_pos == -1: + self.dist_fatal("expected to end with '%s'" % end_with) + + tokens = tokens[start_pos:end_pos] + return self._parse_target_tokens(tokens) + + _parse_regex_arg = re.compile(r'\s|[,]|([+-])') + def _parse_arg_features(self, arg_name, req_features): + if not isinstance(req_features, str): + self.dist_fatal("expected a string in '%s'" % arg_name) + + final_features = set() + # space and comma can be used as a separator + tokens = list(filter(None, re.split(self._parse_regex_arg, req_features))) + append = True # append is the default + for tok in tokens: + if tok[0] in ("#", "$"): + self.dist_fatal( + arg_name, "target groups and policies " + "aren't allowed from arguments, " + "only from dispatch-able sources" + ) + if tok == '+': + append = True + continue + if tok == '-': + append = False + continue + + TOK = tok.upper() # we use upper-case internally + features_to = set() + if TOK == "NONE": + pass + elif TOK == "NATIVE": + native = self.cc_flags["native"] + if not native: + self.dist_fatal(arg_name, + "native option isn't supported by the compiler" + ) + features_to = self.feature_names(force_flags=native) + elif TOK == "MAX": + features_to = self.feature_supported.keys() + elif TOK == "MIN": + features_to = self.feature_min + else: + if TOK in self.feature_supported: + features_to.add(TOK) + else: + if not self.feature_is_exist(TOK): + self.dist_fatal(arg_name, + ", '%s' isn't a known feature or option" % tok + ) + if append: + final_features = final_features.union(features_to) + else: + final_features = final_features.difference(features_to) + + append = True # back to default + + return final_features + + _parse_regex_target = re.compile(r'\s|[*,/]|([()])') + def _parse_target_tokens(self, tokens): + assert(isinstance(tokens, str)) + final_targets = [] # to keep it sorted as specified + extra_flags = [] + has_baseline = False + + skipped = set() + policies = set() + multi_target = None + + tokens = list(filter(None, re.split(self._parse_regex_target, tokens))) + if not tokens: + self.dist_fatal("expected one token at least") + + for tok in tokens: + TOK = tok.upper() + ch = tok[0] + if ch in ('+', '-'): + self.dist_fatal( + "+/- are 'not' allowed from target's groups or @targets, " + "only from cpu_baseline and cpu_dispatch parms" + ) + elif ch == '$': + if multi_target is not None: + self.dist_fatal( + "policies aren't allowed inside multi-target '()'" + ", only CPU features" + ) + policies.add(self._parse_token_policy(TOK)) + elif ch == '#': + if multi_target is not None: + self.dist_fatal( + "target groups aren't allowed inside multi-target '()'" + ", only CPU features" + ) + has_baseline, final_targets, extra_flags = \ + self._parse_token_group(TOK, has_baseline, final_targets, extra_flags) + elif ch == '(': + if multi_target is not None: + self.dist_fatal("unclosed multi-target, missing ')'") + multi_target = set() + elif ch == ')': + if multi_target is None: + self.dist_fatal("multi-target opener '(' wasn't found") + targets = self._parse_multi_target(multi_target) + if targets is None: + skipped.add(tuple(multi_target)) + else: + if len(targets) == 1: + targets = targets[0] + if targets and targets not in final_targets: + final_targets.append(targets) + multi_target = None # back to default + else: + if TOK == "BASELINE": + if multi_target is not None: + self.dist_fatal("baseline isn't allowed inside multi-target '()'") + has_baseline = True + continue + + if multi_target is not None: + multi_target.add(TOK) + continue + + if not self.feature_is_exist(TOK): + self.dist_fatal("invalid target name '%s'" % TOK) + + is_enabled = ( + TOK in self.parse_baseline_names or + TOK in self.parse_dispatch_names + ) + if is_enabled: + if TOK not in final_targets: + final_targets.append(TOK) + continue + + skipped.add(TOK) + + if multi_target is not None: + self.dist_fatal("unclosed multi-target, missing ')'") + if skipped: + self.dist_log( + "skip targets", skipped, + "not part of baseline or dispatch-able features" + ) + + final_targets = self.feature_untied(final_targets) + + # add polices dependencies + for p in list(policies): + _, _, deps = self._parse_policies[p] + for d in deps: + if d in policies: + continue + self.dist_log( + "policy '%s' force enables '%s'" % ( + p, d + )) + policies.add(d) + + # release policies filtrations + for p, (have, nhave, _) in self._parse_policies.items(): + func = None + if p in policies: + func = have + self.dist_log("policy '%s' is ON" % p) + else: + func = nhave + if not func: + continue + has_baseline, final_targets, extra_flags = func( + has_baseline, final_targets, extra_flags + ) + + return has_baseline, final_targets, extra_flags + + def _parse_token_policy(self, token): + """validate policy token""" + if len(token) <= 1 or token[-1:] == token[0]: + self.dist_fatal("'$' must stuck in the begin of policy name") + token = token[1:] + if token not in self._parse_policies: + self.dist_fatal( + "'%s' is an invalid policy name, available policies are" % token, + self._parse_policies.keys() + ) + return token + + def _parse_token_group(self, token, has_baseline, final_targets, extra_flags): + """validate group token""" + if len(token) <= 1 or token[-1:] == token[0]: + self.dist_fatal("'#' must stuck in the begin of group name") + + token = token[1:] + ghas_baseline, gtargets, gextra_flags = self.parse_target_groups.get( + token, (False, None, []) + ) + if gtargets is None: + self.dist_fatal( + "'%s' is an invalid target group name, " % token + \ + "available target groups are", + self.parse_target_groups.keys() + ) + if ghas_baseline: + has_baseline = True + # always keep sorting as specified + final_targets += [f for f in gtargets if f not in final_targets] + extra_flags += [f for f in gextra_flags if f not in extra_flags] + return has_baseline, final_targets, extra_flags + + def _parse_multi_target(self, targets): + """validate multi targets that defined between parentheses()""" + # remove any implied features and keep the origins + if not targets: + self.dist_fatal("empty multi-target '()'") + if not all([ + self.feature_is_exist(tar) for tar in targets + ]) : + self.dist_fatal("invalid target name in multi-target", targets) + if not all([ + ( + tar in self.parse_baseline_names or + tar in self.parse_dispatch_names + ) + for tar in targets + ]) : + return None + targets = self.feature_ahead(targets) + if not targets: + return None + # force sort multi targets, so it can be comparable + targets = self.feature_sorted(targets) + targets = tuple(targets) # hashable + return targets + + def _parse_policy_not_keepbase(self, has_baseline, final_targets, extra_flags): + """skip all baseline features""" + skipped = [] + for tar in final_targets[:]: + is_base = False + if isinstance(tar, str): + is_base = tar in self.parse_baseline_names + else: + # multi targets + is_base = all([ + f in self.parse_baseline_names + for f in tar + ]) + if is_base: + skipped.append(tar) + final_targets.remove(tar) + + if skipped: + self.dist_log("skip baseline features", skipped) + + return has_baseline, final_targets, extra_flags + + def _parse_policy_keepsort(self, has_baseline, final_targets, extra_flags): + """leave a notice that $keep_sort is on""" + self.dist_log( + "policy 'keep_sort' is on, dispatch-able targets", final_targets, "\n" + "are 'not' sorted depend on the highest interest but" + "as specified in the dispatch-able source or the extra group" + ) + return has_baseline, final_targets, extra_flags + + def _parse_policy_not_keepsort(self, has_baseline, final_targets, extra_flags): + """sorted depend on the highest interest""" + final_targets = self.feature_sorted(final_targets, reverse=True) + return has_baseline, final_targets, extra_flags + + def _parse_policy_maxopt(self, has_baseline, final_targets, extra_flags): + """append the compiler optimization flags""" + if self.cc_has_debug: + self.dist_log("debug mode is detected, policy 'maxopt' is skipped.") + elif self.cc_noopt: + self.dist_log("optimization is disabled, policy 'maxopt' is skipped.") + else: + flags = self.cc_flags["opt"] + if not flags: + self.dist_log( + "current compiler doesn't support optimization flags, " + "policy 'maxopt' is skipped", stderr=True + ) + else: + extra_flags += flags + return has_baseline, final_targets, extra_flags + + def _parse_policy_werror(self, has_baseline, final_targets, extra_flags): + """force warnings to treated as errors""" + flags = self.cc_flags["werror"] + if not flags: + self.dist_log( + "current compiler doesn't support werror flags, " + "warnings will 'not' treated as errors", stderr=True + ) + else: + self.dist_log("compiler warnings are treated as errors") + extra_flags += flags + return has_baseline, final_targets, extra_flags + + def _parse_policy_autovec(self, has_baseline, final_targets, extra_flags): + """skip features that has no auto-vectorized support by compiler""" + skipped = [] + for tar in final_targets[:]: + if isinstance(tar, str): + can = self.feature_can_autovec(tar) + else: # multiple target + can = all([ + self.feature_can_autovec(t) + for t in tar + ]) + if not can: + final_targets.remove(tar) + skipped.append(tar) + + if skipped: + self.dist_log("skip non auto-vectorized features", skipped) + + return has_baseline, final_targets, extra_flags + +class CCompilerOpt(_Config, _Distutils, _Cache, _CCompiler, _Feature, _Parse): + """ + A helper class for `CCompiler` aims to provide extra build options + to effectively control of compiler optimizations that are directly + related to CPU features. + """ + def __init__(self, ccompiler, cpu_baseline="min", cpu_dispatch="max", cache_path=None): + _Config.__init__(self) + _Distutils.__init__(self, ccompiler) + _Cache.__init__(self, cache_path, self.dist_info(), cpu_baseline, cpu_dispatch) + _CCompiler.__init__(self) + _Feature.__init__(self) + if not self.cc_noopt and self.cc_has_native: + self.dist_log( + "native flag is specified through environment variables. " + "force cpu-baseline='native'" + ) + cpu_baseline = "native" + _Parse.__init__(self, cpu_baseline, cpu_dispatch) + # keep the requested features untouched, need it later for report + # and trace purposes + self._requested_baseline = cpu_baseline + self._requested_dispatch = cpu_dispatch + # key is the dispatch-able source and value is a tuple + # contains two items (has_baseline[boolean], dispatched-features[list]) + self.sources_status = getattr(self, "sources_status", {}) + # every instance should has a separate one + self.cache_private.add("sources_status") + # set it at the end to make sure the cache writing was done after init + # this class + self.hit_cache = hasattr(self, "hit_cache") + + def is_cached(self): + """ + Returns True if the class loaded from the cache file + """ + return self.cache_infile and self.hit_cache + + def cpu_baseline_flags(self): + """ + Returns a list of final CPU baseline compiler flags + """ + return self.parse_baseline_flags + + def cpu_baseline_names(self): + """ + return a list of final CPU baseline feature names + """ + return self.parse_baseline_names + + def cpu_dispatch_names(self): + """ + return a list of final CPU dispatch feature names + """ + return self.parse_dispatch_names + + def try_dispatch(self, sources, src_dir=None, **kwargs): + """ + Compile one or more dispatch-able sources and generates object files, + also generates abstract C config headers and macros that + used later for the final runtime dispatching process. + + The mechanism behind it is to takes each source file that specified + in 'sources' and branching it into several files depend on + special configuration statements that must be declared in the + top of each source which contains targeted CPU features, + then it compiles every branched source with the proper compiler flags. + + Parameters + ---------- + sources : list + Must be a list of dispatch-able sources file paths, + and configuration statements must be declared inside + each file. + + src_dir : str + Path of parent directory for the generated headers and wrapped sources. + If None(default) the files will generated in-place. + + **kwargs : any + Arguments to pass on to the `CCompiler.compile()` + + Returns + ------- + list : generated object files + + Raises + ------ + CompileError + Raises by `CCompiler.compile()` on compiling failure. + DistutilsError + Some errors during checking the sanity of configuration statements. + + See Also + -------- + parse_targets() : + Parsing the configuration statements of dispatch-able sources. + """ + to_compile = {} + baseline_flags = self.cpu_baseline_flags() + include_dirs = kwargs.setdefault("include_dirs", []) + + for src in sources: + output_dir = os.path.dirname(src) + if src_dir: + if not output_dir.startswith(src_dir): + output_dir = os.path.join(src_dir, output_dir) + if output_dir not in include_dirs: + # To allow including the generated config header(*.dispatch.h) + # by the dispatch-able sources + include_dirs.append(output_dir) + + has_baseline, targets, extra_flags = self.parse_targets(src) + nochange = self._generate_config(output_dir, src, targets, has_baseline) + for tar in targets: + tar_src = self._wrap_target(output_dir, src, tar, nochange=nochange) + flags = tuple(extra_flags + self.feature_flags(tar)) + to_compile.setdefault(flags, []).append(tar_src) + + if has_baseline: + flags = tuple(extra_flags + baseline_flags) + to_compile.setdefault(flags, []).append(src) + + self.sources_status[src] = (has_baseline, targets) + + # For these reasons, the sources are compiled in a separate loop: + # - Gathering all sources with the same flags to benefit from + # the parallel compiling as much as possible. + # - To generate all config headers of the dispatchable sources, + # before the compilation in case if there are dependency relationships + # among them. + objects = [] + for flags, srcs in to_compile.items(): + objects += self.dist_compile(srcs, list(flags), **kwargs) + return objects + + def generate_dispatch_header(self, header_path): + """ + Generate the dispatch header which containing all definitions + and headers of instruction-sets for the enabled CPU baseline and + dispatch-able features. + + Its highly recommended to take a look at the generated header + also the generated source files via `try_dispatch()` + in order to get the full picture. + """ + self.dist_log("generate CPU dispatch header: (%s)" % header_path) + + baseline_names = self.cpu_baseline_names() + dispatch_names = self.cpu_dispatch_names() + baseline_len = len(baseline_names) + dispatch_len = len(dispatch_names) + + with open(header_path, 'w') as f: + baseline_calls = ' \\\n'.join([ + ( + "\t%sWITH_CPU_EXPAND_(MACRO_TO_CALL(%s, __VA_ARGS__))" + ) % (self.conf_c_prefix, f) + for f in baseline_names + ]) + dispatch_calls = ' \\\n'.join([ + ( + "\t%sWITH_CPU_EXPAND_(MACRO_TO_CALL(%s, __VA_ARGS__))" + ) % (self.conf_c_prefix, f) + for f in dispatch_names + ]) + f.write(textwrap.dedent("""\ + /* + * AUTOGENERATED DON'T EDIT + * Please make changes to the code generator (distutils/ccompiler_opt.py) + */ + #define {pfx}WITH_CPU_BASELINE "{baseline_str}" + #define {pfx}WITH_CPU_DISPATCH "{dispatch_str}" + #define {pfx}WITH_CPU_BASELINE_N {baseline_len} + #define {pfx}WITH_CPU_DISPATCH_N {dispatch_len} + #define {pfx}WITH_CPU_EXPAND_(X) X + #define {pfx}WITH_CPU_BASELINE_CALL(MACRO_TO_CALL, ...) \\ + {baseline_calls} + #define {pfx}WITH_CPU_DISPATCH_CALL(MACRO_TO_CALL, ...) \\ + {dispatch_calls} + """).format( + pfx=self.conf_c_prefix, baseline_str=" ".join(baseline_names), + dispatch_str=" ".join(dispatch_names), baseline_len=baseline_len, + dispatch_len=dispatch_len, baseline_calls=baseline_calls, + dispatch_calls=dispatch_calls + )) + baseline_pre = '' + for name in baseline_names: + baseline_pre += self.feature_c_preprocessor(name, tabs=1) + '\n' + + dispatch_pre = '' + for name in dispatch_names: + dispatch_pre += textwrap.dedent("""\ + #ifdef {pfx}CPU_TARGET_{name} + {pre} + #endif /*{pfx}CPU_TARGET_{name}*/ + """).format( + pfx=self.conf_c_prefix_, name=name, pre=self.feature_c_preprocessor( + name, tabs=1 + )) + + f.write(textwrap.dedent("""\ + /******* baseline features *******/ + {baseline_pre} + /******* dispatch features *******/ + {dispatch_pre} + """).format( + pfx=self.conf_c_prefix_, baseline_pre=baseline_pre, + dispatch_pre=dispatch_pre + )) + + def report(self, full=False): + report = [] + baseline_rows = [] + dispatch_rows = [] + report.append(("CPU baseline", baseline_rows)) + report.append(("", "")) + report.append(("CPU dispatch", dispatch_rows)) + + ########## baseline ########## + if self.cc_noopt: + baseline_rows.append(( + "Requested", "optimization disabled %s" % ( + "(unsupported arch)" if self.cc_on_noarch else "" + ) + )) + else: + baseline_rows.append(("Requested", repr(self._requested_baseline))) + + baseline_names = self.cpu_baseline_names() + baseline_rows.append(( + "Enabled", (' '.join(baseline_names) if baseline_names else "none") + )) + baseline_flags = self.cpu_baseline_flags() + baseline_rows.append(( + "Flags", (' '.join(baseline_flags) if baseline_flags else "none") + )) + + ########## dispatch ########## + if self.cc_noopt: + dispatch_rows.append(( + "Requested", "optimization disabled %s" % ( + "(unsupported arch)" if self.cc_on_noarch else "" + ) + )) + else: + dispatch_rows.append(("Requested", repr(self._requested_dispatch))) + + dispatch_names = self.cpu_dispatch_names() + dispatch_rows.append(( + "Enabled", (' '.join(dispatch_names) if dispatch_names else "none") + )) + ########## Generated ########## + # TODO: + # - collect object names from 'try_dispatch()' + # then get size of each object and printed + # - give more details about the features that not + # generated due compiler support + # - find a better output's design. + # + target_sources = {} + for source, (_, targets) in self.sources_status.items(): + for tar in targets: + target_sources.setdefault(tar, []).append(source) + + if not full or not target_sources: + generated = "" + for tar in self.feature_sorted(target_sources): + sources = target_sources[tar] + name = tar if isinstance(tar, str) else '(%s)' % ' '.join(tar) + generated += name + "[%d] " % len(sources) + dispatch_rows.append(("Generated", generated[:-1] if generated else "none")) + else: + dispatch_rows.append(("Generated", '')) + for tar in self.feature_sorted(target_sources): + sources = target_sources[tar] + name = tar if isinstance(tar, str) else '(%s)' % ' '.join(tar) + flags = ' '.join(self.feature_flags(tar)) + implies = ' '.join(self.feature_sorted(self.feature_implies(tar))) + detect = ' '.join(self.feature_detect(tar)) + dispatch_rows.append(('', '')) + dispatch_rows.append((name, implies)) + dispatch_rows.append(("Flags", flags)) + dispatch_rows.append(("Detect", detect)) + for src in sources: + dispatch_rows.append(("", src)) + + ############################### + # TODO: add support for 'markdown' format + text = [] + secs_len = [len(secs) for secs, _ in report] + cols_len = [len(col) for _, rows in report for col, _ in rows] + tab = ' ' * 2 + pad = max(max(secs_len), max(cols_len)) + for sec, rows in report: + if not sec: + text.append("") # empty line + continue + sec += ' ' * (pad - len(sec)) + text.append(sec + tab + ': ') + for col, val in rows: + col += ' ' * (pad - len(col)) + text.append(tab + col + ': ' + val) + + return '\n'.join(text) + + def _wrap_target(self, output_dir, dispatch_src, target, nochange=False): + assert(isinstance(target, (str, tuple))) + if isinstance(target, str): + ext_name = target_name = target + else: + # multi-target + ext_name = '.'.join(target) + target_name = '__'.join(target) + + wrap_path = os.path.join(output_dir, os.path.basename(dispatch_src)) + wrap_path = "{0}.{2}{1}".format(*os.path.splitext(wrap_path), ext_name.lower()) + if nochange and os.path.exists(wrap_path): + return wrap_path + + self.dist_log("wrap dispatch-able target -> ", wrap_path) + # sorting for readability + features = self.feature_sorted(self.feature_implies_c(target)) + target_join = "#define %sCPU_TARGET_" % self.conf_c_prefix_ + target_defs = [target_join + f for f in features] + target_defs = '\n'.join(target_defs) + + with open(wrap_path, "w") as fd: + fd.write(textwrap.dedent("""\ + /** + * AUTOGENERATED DON'T EDIT + * Please make changes to the code generator \ + (distutils/ccompiler_opt.py) + */ + #define {pfx}CPU_TARGET_MODE + #define {pfx}CPU_TARGET_CURRENT {target_name} + {target_defs} + #include "{path}" + """).format( + pfx=self.conf_c_prefix_, target_name=target_name, + path=os.path.abspath(dispatch_src), target_defs=target_defs + )) + return wrap_path + + def _generate_config(self, output_dir, dispatch_src, targets, has_baseline=False): + config_path = os.path.basename(dispatch_src).replace(".c", ".h") + config_path = os.path.join(output_dir, config_path) + # check if targets didn't change to avoid recompiling + cache_hash = self.cache_hash(targets, has_baseline) + try: + with open(config_path) as f: + last_hash = f.readline().split("cache_hash:") + if len(last_hash) == 2 and int(last_hash[1]) == cache_hash: + return True + except IOError: + pass + + self.dist_log("generate dispatched config -> ", config_path) + dispatch_calls = [] + for tar in targets: + if isinstance(tar, str): + target_name = tar + else: # multi target + target_name = '__'.join([t for t in tar]) + req_detect = self.feature_detect(tar) + req_detect = '&&'.join([ + "CHK(%s)" % f for f in req_detect + ]) + dispatch_calls.append( + "\t%sCPU_DISPATCH_EXPAND_(CB((%s), %s, __VA_ARGS__))" % ( + self.conf_c_prefix_, req_detect, target_name + )) + dispatch_calls = ' \\\n'.join(dispatch_calls) + + if has_baseline: + baseline_calls = ( + "\t%sCPU_DISPATCH_EXPAND_(CB(__VA_ARGS__))" + ) % self.conf_c_prefix_ + else: + baseline_calls = '' + + with open(config_path, "w") as fd: + fd.write(textwrap.dedent("""\ + // cache_hash:{cache_hash} + /** + * AUTOGENERATED DON'T EDIT + * Please make changes to the code generator (distutils/ccompiler_opt.py) + */ + #ifndef {pfx}CPU_DISPATCH_EXPAND_ + #define {pfx}CPU_DISPATCH_EXPAND_(X) X + #endif + #undef {pfx}CPU_DISPATCH_BASELINE_CALL + #undef {pfx}CPU_DISPATCH_CALL + #define {pfx}CPU_DISPATCH_BASELINE_CALL(CB, ...) \\ + {baseline_calls} + #define {pfx}CPU_DISPATCH_CALL(CHK, CB, ...) \\ + {dispatch_calls} + """).format( + pfx=self.conf_c_prefix_, baseline_calls=baseline_calls, + dispatch_calls=dispatch_calls, cache_hash=cache_hash + )) + return False + +def new_ccompiler_opt(compiler, **kwargs): + """ + Create a new instance of 'CCompilerOpt' and generate the dispatch header + inside NumPy source dir. + + Parameters + ---------- + 'compiler' : CCompiler instance + '**kwargs': passed as-is to `CCompilerOpt(...)` + + Returns + ------- + new instance of CCompilerOpt + """ + opt = CCompilerOpt(compiler, **kwargs) + npy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + header_dir = os.path.join(npy_path, *("core/src/common".split("/"))) + header_path = os.path.join(header_dir, "_cpu_dispatch.h") + if not os.path.exists(header_path) or not opt.is_cached(): + if not os.path.exists(header_dir): + opt.dist_log( + "dispatch header dir '%s' isn't exist, creating it" % header_dir, + stderr=True + ) + os.makedirs(header_dir) + opt.generate_dispatch_header(header_path) + return opt diff --git a/numpy/distutils/checks/cpu_asimd.c b/numpy/distutils/checks/cpu_asimd.c new file mode 100644 index 000000000..8df556b6c --- /dev/null +++ b/numpy/distutils/checks/cpu_asimd.c @@ -0,0 +1,25 @@ +#ifdef _MSC_VER + #include <Intrin.h> +#endif +#include <arm_neon.h> + +int main(void) +{ + float32x4_t v1 = vdupq_n_f32(1.0f), v2 = vdupq_n_f32(2.0f); + /* MAXMIN */ + int ret = (int)vgetq_lane_f32(vmaxnmq_f32(v1, v2), 0); + ret += (int)vgetq_lane_f32(vminnmq_f32(v1, v2), 0); + /* ROUNDING */ + ret += (int)vgetq_lane_f32(vrndq_f32(v1), 0); +#ifdef __aarch64__ + { + float64x2_t vd1 = vdupq_n_f64(1.0), vd2 = vdupq_n_f64(2.0); + /* MAXMIN */ + ret += (int)vgetq_lane_f64(vmaxnmq_f64(vd1, vd2), 0); + ret += (int)vgetq_lane_f64(vminnmq_f64(vd1, vd2), 0); + /* ROUNDING */ + ret += (int)vgetq_lane_f64(vrndq_f64(vd1), 0); + } +#endif + return ret; +} diff --git a/numpy/distutils/checks/cpu_asimddp.c b/numpy/distutils/checks/cpu_asimddp.c new file mode 100644 index 000000000..0158d1354 --- /dev/null +++ b/numpy/distutils/checks/cpu_asimddp.c @@ -0,0 +1,15 @@ +#ifdef _MSC_VER + #include <Intrin.h> +#endif +#include <arm_neon.h> + +int main(void) +{ + uint8x16_t v1 = vdupq_n_u8((unsigned char)1), v2 = vdupq_n_u8((unsigned char)2); + uint32x4_t va = vdupq_n_u32(3); + int ret = (int)vgetq_lane_u32(vdotq_u32(va, v1, v2), 0); +#ifdef __aarch64__ + ret += (int)vgetq_lane_u32(vdotq_laneq_u32(va, v1, v2, 0), 0); +#endif + return ret; +} diff --git a/numpy/distutils/checks/cpu_asimdfhm.c b/numpy/distutils/checks/cpu_asimdfhm.c new file mode 100644 index 000000000..bb437aa40 --- /dev/null +++ b/numpy/distutils/checks/cpu_asimdfhm.c @@ -0,0 +1,17 @@ +#ifdef _MSC_VER + #include <Intrin.h> +#endif +#include <arm_neon.h> + +int main(void) +{ + float16x8_t vhp = vdupq_n_f16((float16_t)1); + float16x4_t vlhp = vdup_n_f16((float16_t)1); + float32x4_t vf = vdupq_n_f32(1.0f); + float32x2_t vlf = vdup_n_f32(1.0f); + + int ret = (int)vget_lane_f32(vfmlal_low_u32(vlf, vlhp, vlhp), 0); + ret += (int)vgetq_lane_f32(vfmlslq_high_u32(vf, vhp, vhp), 0); + + return ret; +} diff --git a/numpy/distutils/checks/cpu_asimdhp.c b/numpy/distutils/checks/cpu_asimdhp.c new file mode 100644 index 000000000..80b94000f --- /dev/null +++ b/numpy/distutils/checks/cpu_asimdhp.c @@ -0,0 +1,14 @@ +#ifdef _MSC_VER + #include <Intrin.h> +#endif +#include <arm_neon.h> + +int main(void) +{ + float16x8_t vhp = vdupq_n_f16((float16_t)-1); + float16x4_t vlhp = vdup_n_f16((float16_t)-1); + + int ret = (int)vgetq_lane_f16(vabdq_f16(vhp, vhp), 0); + ret += (int)vget_lane_f16(vabd_f16(vlhp, vlhp), 0); + return ret; +} diff --git a/numpy/distutils/checks/cpu_avx.c b/numpy/distutils/checks/cpu_avx.c new file mode 100644 index 000000000..737c0d2e9 --- /dev/null +++ b/numpy/distutils/checks/cpu_avx.c @@ -0,0 +1,7 @@ +#include <immintrin.h> + +int main(void) +{ + __m256 a = _mm256_add_ps(_mm256_setzero_ps(), _mm256_setzero_ps()); + return (int)_mm_cvtss_f32(_mm256_castps256_ps128(a)); +} diff --git a/numpy/distutils/checks/cpu_avx2.c b/numpy/distutils/checks/cpu_avx2.c new file mode 100644 index 000000000..dfb11fd79 --- /dev/null +++ b/numpy/distutils/checks/cpu_avx2.c @@ -0,0 +1,7 @@ +#include <immintrin.h> + +int main(void) +{ + __m256i a = _mm256_abs_epi16(_mm256_setzero_si256()); + return _mm_cvtsi128_si32(_mm256_castsi256_si128(a)); +} diff --git a/numpy/distutils/checks/cpu_avx512_clx.c b/numpy/distutils/checks/cpu_avx512_clx.c new file mode 100644 index 000000000..71dad83a7 --- /dev/null +++ b/numpy/distutils/checks/cpu_avx512_clx.c @@ -0,0 +1,8 @@ +#include <immintrin.h> + +int main(void) +{ + /* VNNI */ + __m512i a = _mm512_dpbusd_epi32(_mm512_setzero_si512(), _mm512_setzero_si512(), _mm512_setzero_si512()); + return _mm_cvtsi128_si32(_mm512_castsi512_si128(a)); +} diff --git a/numpy/distutils/checks/cpu_avx512_cnl.c b/numpy/distutils/checks/cpu_avx512_cnl.c new file mode 100644 index 000000000..dfab4436d --- /dev/null +++ b/numpy/distutils/checks/cpu_avx512_cnl.c @@ -0,0 +1,10 @@ +#include <immintrin.h> + +int main(void) +{ + /* IFMA */ + __m512i a = _mm512_madd52hi_epu64(_mm512_setzero_si512(), _mm512_setzero_si512(), _mm512_setzero_si512()); + /* VMBI */ + a = _mm512_permutex2var_epi8(a, _mm512_setzero_si512(), _mm512_setzero_si512()); + return _mm_cvtsi128_si32(_mm512_castsi512_si128(a)); +} diff --git a/numpy/distutils/checks/cpu_avx512_icl.c b/numpy/distutils/checks/cpu_avx512_icl.c new file mode 100644 index 000000000..cf2706b3b --- /dev/null +++ b/numpy/distutils/checks/cpu_avx512_icl.c @@ -0,0 +1,12 @@ +#include <immintrin.h> + +int main(void) +{ + /* VBMI2 */ + __m512i a = _mm512_shrdv_epi64(_mm512_setzero_si512(), _mm512_setzero_si512(), _mm512_setzero_si512()); + /* BITLAG */ + a = _mm512_popcnt_epi8(a); + /* VPOPCNTDQ */ + a = _mm512_popcnt_epi64(a); + return _mm_cvtsi128_si32(_mm512_castsi512_si128(a)); +} diff --git a/numpy/distutils/checks/cpu_avx512_knl.c b/numpy/distutils/checks/cpu_avx512_knl.c new file mode 100644 index 000000000..0699f37a6 --- /dev/null +++ b/numpy/distutils/checks/cpu_avx512_knl.c @@ -0,0 +1,11 @@ +#include <immintrin.h> + +int main(void) +{ + int base[128]; + /* ER */ + __m512i a = _mm512_castpd_si512(_mm512_exp2a23_pd(_mm512_setzero_pd())); + /* PF */ + _mm512_mask_prefetch_i64scatter_pd(base, _mm512_cmpeq_epi64_mask(a, a), a, 1, _MM_HINT_T1); + return base[0]; +} diff --git a/numpy/distutils/checks/cpu_avx512_knm.c b/numpy/distutils/checks/cpu_avx512_knm.c new file mode 100644 index 000000000..db61b4bfa --- /dev/null +++ b/numpy/distutils/checks/cpu_avx512_knm.c @@ -0,0 +1,17 @@ +#include <immintrin.h> + +int main(void) +{ + __m512i a = _mm512_setzero_si512(); + __m512 b = _mm512_setzero_ps(); + + /* 4FMAPS */ + b = _mm512_4fmadd_ps(b, b, b, b, b, NULL); + /* 4VNNIW */ + a = _mm512_4dpwssd_epi32(a, a, a, a, a, NULL); + /* VPOPCNTDQ */ + a = _mm512_popcnt_epi64(a); + + a = _mm512_add_epi32(a, _mm512_castps_si512(b)); + return _mm_cvtsi128_si32(_mm512_castsi512_si128(a)); +} diff --git a/numpy/distutils/checks/cpu_avx512_skx.c b/numpy/distutils/checks/cpu_avx512_skx.c new file mode 100644 index 000000000..1d5e15b5e --- /dev/null +++ b/numpy/distutils/checks/cpu_avx512_skx.c @@ -0,0 +1,12 @@ +#include <immintrin.h> + +int main(void) +{ + /* VL */ + __m256i a = _mm256_abs_epi64(_mm256_setzero_si256()); + /* DQ */ + __m512i b = _mm512_broadcast_i32x8(a); + /* BW */ + b = _mm512_abs_epi16(b); + return _mm_cvtsi128_si32(_mm512_castsi512_si128(b)); +} diff --git a/numpy/distutils/checks/cpu_avx512cd.c b/numpy/distutils/checks/cpu_avx512cd.c new file mode 100644 index 000000000..61bef6b82 --- /dev/null +++ b/numpy/distutils/checks/cpu_avx512cd.c @@ -0,0 +1,7 @@ +#include <immintrin.h> + +int main(void) +{ + __m512i a = _mm512_lzcnt_epi32(_mm512_setzero_si512()); + return _mm_cvtsi128_si32(_mm512_castsi512_si128(a)); +} diff --git a/numpy/distutils/checks/cpu_avx512f.c b/numpy/distutils/checks/cpu_avx512f.c new file mode 100644 index 000000000..f60cc09dd --- /dev/null +++ b/numpy/distutils/checks/cpu_avx512f.c @@ -0,0 +1,7 @@ +#include <immintrin.h> + +int main(void) +{ + __m512i a = _mm512_abs_epi32(_mm512_setzero_si512()); + return _mm_cvtsi128_si32(_mm512_castsi512_si128(a)); +} diff --git a/numpy/distutils/checks/cpu_f16c.c b/numpy/distutils/checks/cpu_f16c.c new file mode 100644 index 000000000..a5a343e2d --- /dev/null +++ b/numpy/distutils/checks/cpu_f16c.c @@ -0,0 +1,9 @@ +#include <emmintrin.h> +#include <immintrin.h> + +int main(void) +{ + __m128 a = _mm_cvtph_ps(_mm_setzero_si128()); + __m256 a8 = _mm256_cvtph_ps(_mm_setzero_si128()); + return (int)(_mm_cvtss_f32(a) + _mm_cvtss_f32(_mm256_castps256_ps128(a8))); +} diff --git a/numpy/distutils/checks/cpu_fma3.c b/numpy/distutils/checks/cpu_fma3.c new file mode 100644 index 000000000..cf34c6cb1 --- /dev/null +++ b/numpy/distutils/checks/cpu_fma3.c @@ -0,0 +1,8 @@ +#include <xmmintrin.h> +#include <immintrin.h> + +int main(void) +{ + __m256 a = _mm256_fmadd_ps(_mm256_setzero_ps(), _mm256_setzero_ps(), _mm256_setzero_ps()); + return (int)_mm_cvtss_f32(_mm256_castps256_ps128(a)); +} diff --git a/numpy/distutils/checks/cpu_fma4.c b/numpy/distutils/checks/cpu_fma4.c new file mode 100644 index 000000000..1ad717033 --- /dev/null +++ b/numpy/distutils/checks/cpu_fma4.c @@ -0,0 +1,12 @@ +#include <immintrin.h> +#ifdef _MSC_VER + #include <ammintrin.h> +#else + #include <x86intrin.h> +#endif + +int main(void) +{ + __m256 a = _mm256_macc_ps(_mm256_setzero_ps(), _mm256_setzero_ps(), _mm256_setzero_ps()); + return (int)_mm_cvtss_f32(_mm256_castps256_ps128(a)); +} diff --git a/numpy/distutils/checks/cpu_neon.c b/numpy/distutils/checks/cpu_neon.c new file mode 100644 index 000000000..4eab1f384 --- /dev/null +++ b/numpy/distutils/checks/cpu_neon.c @@ -0,0 +1,15 @@ +#ifdef _MSC_VER + #include <Intrin.h> +#endif +#include <arm_neon.h> + +int main(void) +{ + float32x4_t v1 = vdupq_n_f32(1.0f), v2 = vdupq_n_f32(2.0f); + int ret = (int)vgetq_lane_f32(vmulq_f32(v1, v2), 0); +#ifdef __aarch64__ + float64x2_t vd1 = vdupq_n_f64(1.0), vd2 = vdupq_n_f64(2.0); + ret += (int)vgetq_lane_f64(vmulq_f64(vd1, vd2), 0); +#endif + return ret; +} diff --git a/numpy/distutils/checks/cpu_neon_fp16.c b/numpy/distutils/checks/cpu_neon_fp16.c new file mode 100644 index 000000000..745d2e793 --- /dev/null +++ b/numpy/distutils/checks/cpu_neon_fp16.c @@ -0,0 +1,11 @@ +#ifdef _MSC_VER + #include <Intrin.h> +#endif +#include <arm_neon.h> + +int main(void) +{ + short z4[] = {0, 0, 0, 0, 0, 0, 0, 0}; + float32x4_t v_z4 = vcvt_f32_f16((float16x4_t)vld1_s16((const short*)z4)); + return (int)vgetq_lane_f32(v_z4, 0); +} diff --git a/numpy/distutils/checks/cpu_neon_vfpv4.c b/numpy/distutils/checks/cpu_neon_vfpv4.c new file mode 100644 index 000000000..45f7b5d69 --- /dev/null +++ b/numpy/distutils/checks/cpu_neon_vfpv4.c @@ -0,0 +1,19 @@ +#ifdef _MSC_VER + #include <Intrin.h> +#endif +#include <arm_neon.h> + +int main(void) +{ + float32x4_t v1 = vdupq_n_f32(1.0f); + float32x4_t v2 = vdupq_n_f32(2.0f); + float32x4_t v3 = vdupq_n_f32(3.0f); + int ret = (int)vgetq_lane_f32(vfmaq_f32(v1, v2, v3), 0); +#ifdef __aarch64__ + float64x2_t vd1 = vdupq_n_f64(1.0); + float64x2_t vd2 = vdupq_n_f64(2.0); + float64x2_t vd3 = vdupq_n_f64(3.0); + ret += (int)vgetq_lane_f64(vfmaq_f64(vd1, vd2, vd3), 0); +#endif + return ret; +} diff --git a/numpy/distutils/checks/cpu_popcnt.c b/numpy/distutils/checks/cpu_popcnt.c new file mode 100644 index 000000000..e6a80fb40 --- /dev/null +++ b/numpy/distutils/checks/cpu_popcnt.c @@ -0,0 +1,23 @@ +#ifdef _MSC_VER + #include <nmmintrin.h> +#else + #include <popcntintrin.h> +#endif + +int main(void) +{ + long long a = 0; + int b; +#ifdef _MSC_VER + #ifdef _M_X64 + a = _mm_popcnt_u64(1); + #endif + b = _mm_popcnt_u32(1); +#else + #ifdef __x86_64__ + a = __builtin_popcountll(1); + #endif + b = __builtin_popcount(1); +#endif + return (int)a + b; +} diff --git a/numpy/distutils/checks/cpu_sse.c b/numpy/distutils/checks/cpu_sse.c new file mode 100644 index 000000000..bb98bf63c --- /dev/null +++ b/numpy/distutils/checks/cpu_sse.c @@ -0,0 +1,7 @@ +#include <xmmintrin.h> + +int main(void) +{ + __m128 a = _mm_add_ps(_mm_setzero_ps(), _mm_setzero_ps()); + return (int)_mm_cvtss_f32(a); +} diff --git a/numpy/distutils/checks/cpu_sse2.c b/numpy/distutils/checks/cpu_sse2.c new file mode 100644 index 000000000..658afc9b4 --- /dev/null +++ b/numpy/distutils/checks/cpu_sse2.c @@ -0,0 +1,7 @@ +#include <emmintrin.h> + +int main(void) +{ + __m128i a = _mm_add_epi16(_mm_setzero_si128(), _mm_setzero_si128()); + return _mm_cvtsi128_si32(a); +} diff --git a/numpy/distutils/checks/cpu_sse3.c b/numpy/distutils/checks/cpu_sse3.c new file mode 100644 index 000000000..aece1e601 --- /dev/null +++ b/numpy/distutils/checks/cpu_sse3.c @@ -0,0 +1,7 @@ +#include <pmmintrin.h> + +int main(void) +{ + __m128 a = _mm_hadd_ps(_mm_setzero_ps(), _mm_setzero_ps()); + return (int)_mm_cvtss_f32(a); +} diff --git a/numpy/distutils/checks/cpu_sse41.c b/numpy/distutils/checks/cpu_sse41.c new file mode 100644 index 000000000..bfdb9feac --- /dev/null +++ b/numpy/distutils/checks/cpu_sse41.c @@ -0,0 +1,7 @@ +#include <smmintrin.h> + +int main(void) +{ + __m128 a = _mm_floor_ps(_mm_setzero_ps()); + return (int)_mm_cvtss_f32(a); +} diff --git a/numpy/distutils/checks/cpu_sse42.c b/numpy/distutils/checks/cpu_sse42.c new file mode 100644 index 000000000..24f5d93fe --- /dev/null +++ b/numpy/distutils/checks/cpu_sse42.c @@ -0,0 +1,7 @@ +#include <smmintrin.h> + +int main(void) +{ + __m128 a = _mm_hadd_ps(_mm_setzero_ps(), _mm_setzero_ps()); + return (int)_mm_cvtss_f32(a); +} diff --git a/numpy/distutils/checks/cpu_ssse3.c b/numpy/distutils/checks/cpu_ssse3.c new file mode 100644 index 000000000..ad0abc1e6 --- /dev/null +++ b/numpy/distutils/checks/cpu_ssse3.c @@ -0,0 +1,7 @@ +#include <tmmintrin.h> + +int main(void) +{ + __m128i a = _mm_hadd_epi16(_mm_setzero_si128(), _mm_setzero_si128()); + return (int)_mm_cvtsi128_si32(a); +} diff --git a/numpy/distutils/checks/cpu_vsx.c b/numpy/distutils/checks/cpu_vsx.c new file mode 100644 index 000000000..0b3f30d6a --- /dev/null +++ b/numpy/distutils/checks/cpu_vsx.c @@ -0,0 +1,21 @@ +#ifndef __VSX__ + #error "VSX is not supported" +#endif +#include <altivec.h> + +#if (defined(__GNUC__) && !defined(vec_xl)) || (defined(__clang__) && !defined(__IBMC__)) + #define vsx_ld vec_vsx_ld + #define vsx_st vec_vsx_st +#else + #define vsx_ld vec_xl + #define vsx_st vec_xst +#endif + +int main(void) +{ + unsigned int zout[4]; + unsigned int z4[] = {0, 0, 0, 0}; + __vector unsigned int v_z4 = vsx_ld(0, z4); + vsx_st(v_z4, 0, zout); + return zout[0]; +} diff --git a/numpy/distutils/checks/cpu_vsx2.c b/numpy/distutils/checks/cpu_vsx2.c new file mode 100644 index 000000000..410fb29d6 --- /dev/null +++ b/numpy/distutils/checks/cpu_vsx2.c @@ -0,0 +1,13 @@ +#ifndef __VSX__ + #error "VSX is not supported" +#endif +#include <altivec.h> + +typedef __vector unsigned long long v_uint64x2; + +int main(void) +{ + v_uint64x2 z2 = (v_uint64x2){0, 0}; + z2 = (v_uint64x2)vec_cmpeq(z2, z2); + return (int)vec_extract(z2, 0); +} diff --git a/numpy/distutils/checks/cpu_vsx3.c b/numpy/distutils/checks/cpu_vsx3.c new file mode 100644 index 000000000..857526535 --- /dev/null +++ b/numpy/distutils/checks/cpu_vsx3.c @@ -0,0 +1,13 @@ +#ifndef __VSX__ + #error "VSX is not supported" +#endif +#include <altivec.h> + +typedef __vector unsigned int v_uint32x4; + +int main(void) +{ + v_uint32x4 z4 = (v_uint32x4){0, 0, 0, 0}; + z4 = vec_absd(z4, z4); + return (int)vec_extract(z4, 0); +} diff --git a/numpy/distutils/checks/cpu_xop.c b/numpy/distutils/checks/cpu_xop.c new file mode 100644 index 000000000..51d70cf2b --- /dev/null +++ b/numpy/distutils/checks/cpu_xop.c @@ -0,0 +1,12 @@ +#include <immintrin.h> +#ifdef _MSC_VER + #include <ammintrin.h> +#else + #include <x86intrin.h> +#endif + +int main(void) +{ + __m128i a = _mm_comge_epu32(_mm_setzero_si128(), _mm_setzero_si128()); + return _mm_cvtsi128_si32(a); +} diff --git a/numpy/distutils/checks/test_flags.c b/numpy/distutils/checks/test_flags.c new file mode 100644 index 000000000..4cd09d42a --- /dev/null +++ b/numpy/distutils/checks/test_flags.c @@ -0,0 +1 @@ +int test_flags; diff --git a/numpy/distutils/command/build.py b/numpy/distutils/command/build.py index a156a7c6e..60ba4c917 100644 --- a/numpy/distutils/command/build.py +++ b/numpy/distutils/command/build.py @@ -16,6 +16,12 @@ class build(old_build): "specify the Fortran compiler type"), ('warn-error', None, "turn all warnings into errors (-Werror)"), + ('cpu-baseline=', None, + "specify a list of enabled baseline CPU optimizations"), + ('cpu-dispatch=', None, + "specify a list of dispatched CPU optimizations"), + ('disable-optimization', None, + "disable CPU optimized code(dispatch,simd,fast...)"), ] help_options = old_build.help_options + [ @@ -27,6 +33,9 @@ class build(old_build): old_build.initialize_options(self) self.fcompiler = None self.warn_error = False + self.cpu_baseline = "min" + self.cpu_dispatch = "max -xop -fma4" # drop AMD legacy features by default + self.disable_optimization = False def finalize_options(self): build_scripts = self.build_scripts diff --git a/numpy/distutils/command/build_clib.py b/numpy/distutils/command/build_clib.py index f6a84e351..87345adbc 100644 --- a/numpy/distutils/command/build_clib.py +++ b/numpy/distutils/command/build_clib.py @@ -13,6 +13,7 @@ from numpy.distutils.misc_util import ( filter_sources, get_lib_source_files, get_numpy_include_dirs, has_cxx_sources, has_f_sources, is_sequence ) +from numpy.distutils.ccompiler_opt import new_ccompiler_opt # Fix Python distutils bug sf #1718574: _l = old_build_clib.user_options @@ -34,9 +35,16 @@ class build_clib(old_build_clib): "number of parallel jobs"), ('warn-error', None, "turn all warnings into errors (-Werror)"), + ('cpu-baseline=', None, + "specify a list of enabled baseline CPU optimizations"), + ('cpu-dispatch=', None, + "specify a list of dispatched CPU optimizations"), + ('disable-optimization', None, + "disable CPU optimized code(dispatch,simd,fast...)"), ] - boolean_options = old_build_clib.boolean_options + ['inplace', 'warn-error'] + boolean_options = old_build_clib.boolean_options + \ + ['inplace', 'warn-error', 'disable-optimization'] def initialize_options(self): old_build_clib.initialize_options(self) @@ -44,6 +52,10 @@ class build_clib(old_build_clib): self.inplace = 0 self.parallel = None self.warn_error = None + self.cpu_baseline = None + self.cpu_dispatch = None + self.disable_optimization = None + def finalize_options(self): if self.parallel: @@ -55,6 +67,9 @@ class build_clib(old_build_clib): self.set_undefined_options('build', ('parallel', 'parallel'), ('warn_error', 'warn_error'), + ('cpu_baseline', 'cpu_baseline'), + ('cpu_dispatch', 'cpu_dispatch'), + ('disable_optimization', 'disable_optimization') ) def have_f_sources(self): @@ -102,6 +117,25 @@ class build_clib(old_build_clib): self.compiler.show_customization() + if not self.disable_optimization: + opt_cache_path = os.path.abspath( + os.path.join(self.build_temp, 'ccompiler_opt_cache_clib.py' + )) + self.compiler_opt = new_ccompiler_opt( + compiler=self.compiler, cpu_baseline=self.cpu_baseline, + cpu_dispatch=self.cpu_dispatch, cache_path=opt_cache_path + ) + if not self.compiler_opt.is_cached(): + log.info("Detected changes on compiler optimizations, force rebuilding") + self.force = True + + import atexit + def report(): + log.info("\n########### CLIB COMPILER OPTIMIZATION ###########") + log.info(self.compiler_opt.report(full=True)) + + atexit.register(report) + if self.have_f_sources(): from numpy.distutils.fcompiler import new_fcompiler self._f_compiler = new_fcompiler(compiler=self.fcompiler, @@ -211,6 +245,8 @@ class build_clib(old_build_clib): 'extra_f90_compile_args') or [] macros = build_info.get('macros') + if macros is None: + macros = [] include_dirs = build_info.get('include_dirs') if include_dirs is None: include_dirs = [] @@ -223,6 +259,31 @@ class build_clib(old_build_clib): if requiref90: self.mkpath(module_build_dir) + dispatch_objects = [] + if not self.disable_optimization: + dispatch_sources = [ + c_sources.pop(c_sources.index(src)) + for src in c_sources[:] if src.endswith(".dispatch.c") + ] + if dispatch_sources: + if not self.inplace: + build_src = self.get_finalized_command("build_src").build_src + else: + build_src = None + dispatch_objects = self.compiler_opt.try_dispatch( + dispatch_sources, + output_dir=self.build_temp, + src_dir=build_src, + macros=macros, + include_dirs=include_dirs, + debug=self.debug, + extra_postargs=extra_postargs + ) + extra_args_baseopt = extra_postargs + self.compiler_opt.cpu_baseline_flags() + else: + extra_args_baseopt = extra_postargs + macros.append(("NPY_DISABLE_OPTIMIZATION", 1)) + if compiler.compiler_type == 'msvc': # this hack works around the msvc compiler attributes # problem, msvc uses its own convention :( @@ -237,7 +298,8 @@ class build_clib(old_build_clib): macros=macros, include_dirs=include_dirs, debug=self.debug, - extra_postargs=extra_postargs) + extra_postargs=extra_args_baseopt) + objects.extend(dispatch_objects) if cxx_sources: log.info("compiling C++ sources") diff --git a/numpy/distutils/command/build_ext.py b/numpy/distutils/command/build_ext.py index d53285c92..b6557fcf6 100644 --- a/numpy/distutils/command/build_ext.py +++ b/numpy/distutils/command/build_ext.py @@ -19,7 +19,7 @@ from numpy.distutils.misc_util import ( has_cxx_sources, has_f_sources, is_sequence ) from numpy.distutils.command.config_compiler import show_fortran_compilers - +from numpy.distutils.ccompiler_opt import new_ccompiler_opt class build_ext (old_build_ext): @@ -33,6 +33,12 @@ class build_ext (old_build_ext): "number of parallel jobs"), ('warn-error', None, "turn all warnings into errors (-Werror)"), + ('cpu-baseline=', None, + "specify a list of enabled baseline CPU optimizations"), + ('cpu-dispatch=', None, + "specify a list of dispatched CPU optimizations"), + ('disable-optimization', None, + "disable CPU optimized code(dispatch,simd,fast...)"), ] help_options = old_build_ext.help_options + [ @@ -40,13 +46,16 @@ class build_ext (old_build_ext): show_fortran_compilers), ] - boolean_options = old_build_ext.boolean_options + ['warn-error'] + boolean_options = old_build_ext.boolean_options + ['warn-error', 'disable-optimization'] def initialize_options(self): old_build_ext.initialize_options(self) self.fcompiler = None self.parallel = None self.warn_error = None + self.cpu_baseline = None + self.cpu_dispatch = None + self.disable_optimization = None def finalize_options(self): if self.parallel: @@ -75,6 +84,9 @@ class build_ext (old_build_ext): self.set_undefined_options('build', ('parallel', 'parallel'), ('warn_error', 'warn_error'), + ('cpu_baseline', 'cpu_baseline'), + ('cpu_dispatch', 'cpu_dispatch'), + ('disable_optimization', 'disable_optimization'), ) def run(self): @@ -129,6 +141,22 @@ class build_ext (old_build_ext): self.compiler.show_customization() + if not self.disable_optimization: + opt_cache_path = os.path.abspath(os.path.join(self.build_temp, 'ccompiler_opt_cache_ext.py')) + self.compiler_opt = new_ccompiler_opt(compiler=self.compiler, + cpu_baseline=self.cpu_baseline, + cpu_dispatch=self.cpu_dispatch, + cache_path=opt_cache_path) + if not self.compiler_opt.is_cached(): + log.info("Detected changes on compiler optimizations, force rebuilding") + self.force = True + + import atexit + def report(): + log.info("\n########### EXT COMPILER OPTIMIZATION ###########") + log.info(self.compiler_opt.report(full=True)) + atexit.register(report) + # Setup directory for storing generated extra DLL files on Windows self.extra_dll_dir = os.path.join(self.build_temp, '.libs') if not os.path.isdir(self.extra_dll_dir): @@ -378,6 +406,32 @@ class build_ext (old_build_ext): include_dirs = ext.include_dirs + get_numpy_include_dirs() + dispatch_objects = [] + if not self.disable_optimization: + dispatch_sources = [ + c_sources.pop(c_sources.index(src)) + for src in c_sources[:] if src.endswith(".dispatch.c") + ] + if dispatch_sources: + if not self.inplace: + build_src = self.get_finalized_command("build_src").build_src + else: + build_src = None + dispatch_objects = self.compiler_opt.try_dispatch( + dispatch_sources, + output_dir=output_dir, + src_dir=build_src, + macros=macros, + include_dirs=include_dirs, + debug=self.debug, + extra_postargs=extra_args, + **kws + ) + extra_args_baseopt = extra_args + self.compiler_opt.cpu_baseline_flags() + else: + extra_args_baseopt = extra_args + macros.append(("NPY_DISABLE_OPTIMIZATION", 1)) + c_objects = [] if c_sources: log.info("compiling C sources") @@ -386,8 +440,9 @@ class build_ext (old_build_ext): macros=macros, include_dirs=include_dirs, debug=self.debug, - extra_postargs=extra_args, + extra_postargs=extra_args_baseopt, **kws) + c_objects.extend(dispatch_objects) if cxx_sources: log.info("compiling C++ sources") diff --git a/numpy/distutils/fcompiler/gnu.py b/numpy/distutils/fcompiler/gnu.py index 23d905393..caa08549e 100644 --- a/numpy/distutils/fcompiler/gnu.py +++ b/numpy/distutils/fcompiler/gnu.py @@ -253,15 +253,20 @@ class GnuFCompiler(FCompiler): return [] def runtime_library_dir_option(self, dir): - if sys.platform[:3] == 'aix' or sys.platform == 'win32': - # Linux/Solaris/Unix support RPATH, Windows and AIX do not + if sys.platform == 'win32': + # Linux/Solaris/Unix support RPATH, Windows does not raise NotImplementedError # TODO: could use -Xlinker here, if it's supported assert "," not in dir - sep = ',' if sys.platform == 'darwin' else '=' - return '-Wl,-rpath%s%s' % (sep, dir) + if sys.platform == 'darwin': + return f'-Wl,-rpath,{dir}' + elif sys.platform[:3] == 'aix': + # AIX RPATH is called LIBPATH + return f'-Wl,-blibpath:{dir}' + else: + return f'-Wl,-rpath={dir}' class Gnu95FCompiler(GnuFCompiler): diff --git a/numpy/distutils/misc_util.py b/numpy/distutils/misc_util.py index 9f9e9f1ac..d2a3149f7 100644 --- a/numpy/distutils/misc_util.py +++ b/numpy/distutils/misc_util.py @@ -1900,7 +1900,7 @@ class Configuration: revision0 = f.read().strip() branch_map = {} - for line in file(branch_cache_fn, 'r'): + for line in open(branch_cache_fn, 'r'): branch1, revision1 = line.split()[:2] if revision1==revision0: branch0 = branch1 diff --git a/numpy/distutils/setup.py b/numpy/distutils/setup.py index 88cd1a160..798c3686f 100644 --- a/numpy/distutils/setup.py +++ b/numpy/distutils/setup.py @@ -7,6 +7,7 @@ def configuration(parent_package='',top_path=None): config.add_subpackage('tests') config.add_data_files('site.cfg') config.add_data_files('mingw/gfortran_vs2003_hack.c') + config.add_data_dir('checks') config.make_config_py() return config diff --git a/numpy/distutils/tests/test_ccompiler_opt.py b/numpy/distutils/tests/test_ccompiler_opt.py new file mode 100644 index 000000000..a789be1ea --- /dev/null +++ b/numpy/distutils/tests/test_ccompiler_opt.py @@ -0,0 +1,787 @@ +import re, textwrap, os +from os import sys, path +from distutils.errors import DistutilsError + +is_standalone = __name__ == '__main__' and __package__ is None +if is_standalone: + import unittest, contextlib, tempfile, shutil + sys.path.append(path.abspath(path.join(path.dirname(__file__), ".."))) + from ccompiler_opt import CCompilerOpt + + # from numpy/testing/_private/utils.py + @contextlib.contextmanager + def tempdir(*args, **kwargs): + tmpdir = tempfile.mkdtemp(*args, **kwargs) + try: + yield tmpdir + finally: + shutil.rmtree(tmpdir) + + def assert_(expr, msg=''): + if not expr: + raise AssertionError(msg) +else: + from numpy.distutils.ccompiler_opt import CCompilerOpt + from numpy.testing import assert_, tempdir + +# architectures and compilers to test +arch_compilers = dict( + x86 = ("gcc", "clang", "icc", "iccw", "msvc"), + x64 = ("gcc", "clang", "icc", "iccw", "msvc"), + ppc64 = ("gcc", "clang"), + ppc64le = ("gcc", "clang"), + armhf = ("gcc", "clang"), + aarch64 = ("gcc", "clang"), + noarch = ("gcc",) +) + +class FakeCCompilerOpt(CCompilerOpt): + fake_info = "" + def __init__(self, trap_files="", trap_flags="", *args, **kwargs): + self.fake_trap_files = trap_files + self.fake_trap_flags = trap_flags + CCompilerOpt.__init__(self, None, **kwargs) + + def __repr__(self): + return textwrap.dedent("""\ + <<<< + march : {} + compiler : {} + ---------------- + {} + >>>> + """).format(self.cc_march, self.cc_name, self.report()) + + def dist_compile(self, sources, flags, **kwargs): + assert(isinstance(sources, list)) + assert(isinstance(flags, list)) + if self.fake_trap_files: + for src in sources: + if re.match(self.fake_trap_files, src): + self.dist_error("source is trapped by a fake interface") + if self.fake_trap_flags: + for f in flags: + if re.match(self.fake_trap_flags, f): + self.dist_error("flag is trapped by a fake interface") + # fake objects + return zip(sources, [' '.join(flags)] * len(sources)) + + def dist_info(self): + return FakeCCompilerOpt.fake_info + + @staticmethod + def dist_log(*args, stderr=False): + pass + +class _Test_CCompilerOpt(object): + arch = None # x86_64 + cc = None # gcc + + def setup(self): + FakeCCompilerOpt.conf_nocache = True + self._opt = None + + def nopt(self, *args, **kwargs): + FakeCCompilerOpt.fake_info = self.arch + '_' + self.cc + return FakeCCompilerOpt(*args, **kwargs) + + def opt(self): + if not self._opt: + self._opt = self.nopt() + return self._opt + + def march(self): + return self.opt().cc_march + + def cc_name(self): + return self.opt().cc_name + + def get_targets(self, targets, groups, **kwargs): + FakeCCompilerOpt.conf_target_groups = groups + opt = self.nopt( + cpu_baseline=kwargs.get("baseline", "min"), + cpu_dispatch=kwargs.get("dispatch", "max"), + trap_files=kwargs.get("trap_files", ""), + trap_flags=kwargs.get("trap_flags", "") + ) + with tempdir() as tmpdir: + file = os.path.join(tmpdir, "test_targets.c") + with open(file, 'w') as f: + f.write(targets) + gtargets = [] + gflags = {} + fake_objects = opt.try_dispatch([file]) + for source, flags in fake_objects: + gtar = source.split('.')[1:-1] + glen = len(gtar) + if glen == 0: + gtar = "baseline" + elif glen == 1: + gtar = gtar[0].upper() + else: + # converting multi-target into parentheses str format to be equivalent + # to the configuration statements syntax. + gtar = ('('+' '.join(gtar)+')').upper() + gtargets.append(gtar) + gflags[gtar] = flags + + has_baseline, targets = opt.sources_status[file] + targets = targets + ["baseline"] if has_baseline else targets + # convert tuple that represent multi-target into parentheses str format + targets = [ + '('+' '.join(tar)+')' if isinstance(tar, tuple) else tar + for tar in targets + ] + if len(targets) != len(gtargets) or not all(t in gtargets for t in targets): + raise AssertionError( + "'sources_status' returns different targets than the compiled targets\n" + "%s != %s" % (targets, gtargets) + ) + # return targets from 'sources_status' since the order is matters + return targets, gflags + + def arg_regex(self, **kwargs): + map2origin = dict( + x64 = "x86", + ppc64le = "ppc64", + aarch64 = "armhf", + clang = "gcc", + ) + march = self.march(); cc_name = self.cc_name() + map_march = map2origin.get(march, march) + map_cc = map2origin.get(cc_name, cc_name) + for key in ( + march, cc_name, map_march, map_cc, + march + '_' + cc_name, + map_march + '_' + cc_name, + march + '_' + map_cc, + map_march + '_' + map_cc, + ) : + regex = kwargs.pop(key, None) + if regex is not None: + break + if regex: + if isinstance(regex, dict): + for k, v in regex.items(): + if v[-1:] not in ')}$?\\.+*': + regex[k] = v + '$' + else: + assert(isinstance(regex, str)) + if regex[-1:] not in ')}$?\\.+*': + regex += '$' + return regex + + def expect(self, dispatch, baseline="", **kwargs): + match = self.arg_regex(**kwargs) + if match is None: + return + opt = self.nopt( + cpu_baseline=baseline, cpu_dispatch=dispatch, + trap_files=kwargs.get("trap_files", ""), + trap_flags=kwargs.get("trap_flags", "") + ) + features = ' '.join(opt.cpu_dispatch_names()) + if not match: + if len(features) != 0: + raise AssertionError( + 'expected empty features, not "%s"' % features + ) + return + if not re.match(match, features, re.IGNORECASE): + raise AssertionError( + 'dispatch features "%s" not match "%s"' % (features, match) + ) + + def expect_baseline(self, baseline, dispatch="", **kwargs): + match = self.arg_regex(**kwargs) + if match is None: + return + opt = self.nopt( + cpu_baseline=baseline, cpu_dispatch=dispatch, + trap_files=kwargs.get("trap_files", ""), + trap_flags=kwargs.get("trap_flags", "") + ) + features = ' '.join(opt.cpu_baseline_names()) + if not match: + if len(features) != 0: + raise AssertionError( + 'expected empty features, not "%s"' % features + ) + return + if not re.match(match, features, re.IGNORECASE): + raise AssertionError( + 'baseline features "%s" not match "%s"' % (features, match) + ) + + def expect_flags(self, baseline, dispatch="", **kwargs): + match = self.arg_regex(**kwargs) + if match is None: + return + opt = self.nopt( + cpu_baseline=baseline, cpu_dispatch=dispatch, + trap_files=kwargs.get("trap_files", ""), + trap_flags=kwargs.get("trap_flags", "") + ) + flags = ' '.join(opt.cpu_baseline_flags()) + if not match: + if len(flags) != 0: + raise AssertionError( + 'expected empty flags not "%s"' % flags + ) + return + if not re.match(match, flags): + raise AssertionError( + 'flags "%s" not match "%s"' % (flags, match) + ) + + def expect_targets(self, targets, groups={}, **kwargs): + match = self.arg_regex(**kwargs) + if match is None: + return + targets, _ = self.get_targets(targets=targets, groups=groups, **kwargs) + targets = ' '.join(targets) + if not match: + if len(targets) != 0: + raise AssertionError( + 'expected empty targets, not "%s"' % targets + ) + return + if not re.match(match, targets, re.IGNORECASE): + raise AssertionError( + 'targets "%s" not match "%s"' % (targets, match) + ) + + def expect_target_flags(self, targets, groups={}, **kwargs): + match_dict = self.arg_regex(**kwargs) + if match_dict is None: + return + assert(isinstance(match_dict, dict)) + _, tar_flags = self.get_targets(targets=targets, groups=groups) + + for match_tar, match_flags in match_dict.items(): + if match_tar not in tar_flags: + raise AssertionError( + 'expected to find target "%s"' % match_tar + ) + flags = tar_flags[match_tar] + if not match_flags: + if len(flags) != 0: + raise AssertionError( + 'expected to find empty flags in target "%s"' % match_tar + ) + if not re.match(match_flags, flags): + raise AssertionError( + '"%s" flags "%s" not match "%s"' % (match_tar, flags, match_flags) + ) + + def test_interface(self): + wrong_arch = "ppc64" if self.arch != "ppc64" else "x86" + wrong_cc = "clang" if self.cc != "clang" else "icc" + opt = self.opt() + assert_(getattr(opt, "cc_on_" + self.arch)) + assert_(not getattr(opt, "cc_on_" + wrong_arch)) + assert_(getattr(opt, "cc_is_" + self.cc)) + assert_(not getattr(opt, "cc_is_" + wrong_cc)) + + def test_args_empty(self): + for baseline, dispatch in ( + ("", "none"), + (None, ""), + ("none +none", "none - none"), + ("none -max", "min - max"), + ("+vsx2 -VSX2", "vsx avx2 avx512f -max"), + ("max -vsx - avx + avx512f neon -MAX ", + "min -min + max -max -vsx + avx2 -avx2 +NONE") + ) : + opt = self.nopt(cpu_baseline=baseline, cpu_dispatch=dispatch) + assert(len(opt.cpu_baseline_names()) == 0) + assert(len(opt.cpu_dispatch_names()) == 0) + + def test_args_validation(self): + if self.march() == "unknown": + return + # check sanity of argument's validation + for baseline, dispatch in ( + ("unkown_feature - max +min", "unknown max min"), # unknowing features + ("#avx2", "$vsx") # groups and polices aren't acceptable + ) : + try: + self.nopt(cpu_baseline=baseline, cpu_dispatch=dispatch) + raise AssertionError("excepted an exception for invalid arguments") + except DistutilsError: + pass + + def test_skip(self): + # only takes what platform supports and skip the others + # without casing exceptions + self.expect( + "sse vsx neon", + x86="sse", ppc64="vsx", armhf="neon", unknown="" + ) + self.expect( + "sse41 avx avx2 vsx2 vsx3 neon_vfpv4 asimd", + x86 = "sse41 avx avx2", + ppc64 = "vsx2 vsx3", + armhf = "neon_vfpv4 asimd", + unknown = "" + ) + # any features in cpu_dispatch must be ignored if it's part of baseline + self.expect( + "sse neon vsx", baseline="sse neon vsx", + x86="", ppc64="", armhf="" + ) + self.expect( + "avx2 vsx3 asimdhp", baseline="avx2 vsx3 asimdhp", + x86="", ppc64="", armhf="" + ) + + def test_implies(self): + # baseline combining implied features, so we count + # on it instead of testing 'feature_implies()'' directly + self.expect_baseline( + "fma3 avx2 asimd vsx3", + # .* between two spaces can validate features in between + x86 = "sse .* sse41 .* fma3.*avx2", + ppc64 = "vsx vsx2 vsx3", + armhf = "neon neon_fp16 neon_vfpv4 asimd" + ) + """ + special cases + """ + # in icc and msvc, FMA3 and AVX2 can't be separated + # both need to implies each other, same for avx512f & cd + for f0, f1 in ( + ("fma3", "avx2"), + ("avx512f", "avx512cd"), + ): + diff = ".* sse42 .* %s .*%s$" % (f0, f1) + self.expect_baseline(f0, + x86_gcc=".* sse42 .* %s$" % f0, + x86_icc=diff, x86_iccw=diff + ) + self.expect_baseline(f1, + x86_gcc=".* avx .* %s$" % f1, + x86_icc=diff, x86_iccw=diff + ) + # in msvc, following features can't be separated too + for f in (("fma3", "avx2"), ("avx512f", "avx512cd", "avx512_skx")): + for ff in f: + self.expect_baseline(ff, + x86_msvc=".*%s" % ' '.join(f) + ) + + # in ppc64le VSX and VSX2 can't be separated + self.expect_baseline("vsx", ppc64le="vsx vsx2") + # in aarch64 following features can't be separated + for f in ("neon", "neon_fp16", "neon_vfpv4", "asimd"): + self.expect_baseline(f, aarch64="neon neon_fp16 neon_vfpv4 asimd") + + def test_args_options(self): + # max & native + for o in ("max", "native"): + if o == "native" and self.cc_name() == "msvc": + continue + self.expect(o, + trap_files=".*cpu_(sse|vsx|neon).c", + x86="", ppc64="", armhf="" + ) + self.expect(o, + trap_files=".*cpu_(sse3|vsx2|neon_vfpv4).c", + x86="sse sse2", ppc64="vsx", armhf="neon neon_fp16", + aarch64="", ppc64le="" + ) + self.expect(o, + trap_files=".*cpu_(popcnt|vsx3).c", + x86="sse .* sse41", ppc64="vsx vsx2", + armhf="neon neon_fp16 .* asimd .*" + ) + self.expect(o, + x86_gcc=".* xop fma4 .* avx512f .* avx512_knl avx512_knm avx512_skx .*", + # in icc, xop and fam4 aren't supported + x86_icc=".* avx512f .* avx512_knl avx512_knm avx512_skx .*", + x86_iccw=".* avx512f .* avx512_knl avx512_knm avx512_skx .*", + # in msvc, avx512_knl avx512_knm aren't supported + x86_msvc=".* xop fma4 .* avx512f .* avx512_skx .*", + armhf=".* asimd asimdhp asimddp .*", + ppc64="vsx vsx2 vsx3.*" + ) + # min + self.expect("min", + x86="sse sse2", x64="sse sse2 sse3", + armhf="", aarch64="neon neon_fp16 .* asimd", + ppc64="", ppc64le="vsx vsx2" + ) + self.expect( + "min", trap_files=".*cpu_(sse2|vsx2).c", + x86="", ppc64le="" + ) + # an exception must triggered if native flag isn't supported + # when option "native" is activated through the args + try: + self.expect("native", + trap_flags=".*(-march=native|-xHost|/QxHost).*", + x86=".*", ppc64=".*", armhf=".*" + ) + if self.march() != "unknown": + raise AssertionError( + "excepted an exception for %s" % self.march() + ) + except DistutilsError: + if self.march() == "unknown": + raise AssertionError("excepted no exceptions") + + def test_flags(self): + self.expect_flags( + "sse sse2 vsx vsx2 neon neon_fp16", + x86_gcc="-msse -msse2", x86_icc="-msse -msse2", + x86_iccw="/arch:SSE2", x86_msvc="/arch:SSE2", + ppc64_gcc= "-mcpu=power8", + ppc64_clang="-maltivec -mvsx -mpower8-vector", + armhf_gcc="-mfpu=neon-fp16 -mfp16-format=ieee", + aarch64="" + ) + # testing normalize -march + self.expect_flags( + "asimd", + aarch64="", + armhf_gcc=r"-mfp16-format=ieee -mfpu=neon-fp-armv8 -march=armv8-a\+simd" + ) + self.expect_flags( + "asimdhp", + aarch64_gcc=r"-march=armv8.2-a\+fp16", + armhf_gcc=r"-mfp16-format=ieee -mfpu=neon-fp-armv8 -march=armv8.2-a\+fp16" + ) + self.expect_flags( + "asimddp", aarch64_gcc=r"-march=armv8.2-a\+dotprod" + ) + self.expect_flags( + # asimdfhm implies asimdhp + "asimdfhm", aarch64_gcc=r"-march=armv8.2-a\+fp16\+fp16fml" + ) + self.expect_flags( + "asimddp asimdhp asimdfhm", + aarch64_gcc=r"-march=armv8.2-a\+dotprod\+fp16\+fp16fml" + ) + + def test_targets_exceptions(self): + for targets in ( + "bla bla", "/*@targets", + "/*@targets */", + "/*@targets unknown */", + "/*@targets $unknown_policy avx2 */", + "/*@targets #unknown_group avx2 */", + "/*@targets $ */", + "/*@targets # vsx */", + "/*@targets #$ vsx */", + "/*@targets vsx avx2 ) */", + "/*@targets vsx avx2 (avx2 */", + "/*@targets vsx avx2 () */", + "/*@targets vsx avx2 ($autovec) */", # no features + "/*@targets vsx avx2 (xxx) */", + "/*@targets vsx avx2 (baseline) */", + ) : + try: + self.expect_targets( + targets, + x86="", armhf="", ppc64="" + ) + if self.march() != "unknown": + raise AssertionError( + "excepted an exception for %s" % self.march() + ) + except DistutilsError: + if self.march() == "unknown": + raise AssertionError("excepted no exceptions") + + def test_targets_syntax(self): + for targets in ( + "/*@targets $keep_baseline sse vsx neon*/", + "/*@targets,$keep_baseline,sse,vsx,neon*/", + "/*@targets*$keep_baseline*sse*vsx*neon*/", + """ + /* + ** @targets + ** $keep_baseline, sse vsx,neon + */ + """, + """ + /* + ************@targets************* + ** $keep_baseline, sse vsx, neon + ********************************* + */ + """, + """ + /* + /////////////@targets///////////////// + //$keep_baseline//sse//vsx//neon + ///////////////////////////////////// + */ + """, + """ + /* + @targets + $keep_baseline + SSE VSX NEON*/ + """ + ) : + self.expect_targets(targets, + x86="sse", ppc64="vsx", armhf="neon", unknown="" + ) + + def test_targets(self): + # test skipping baseline features + self.expect_targets( + """ + /*@targets + sse sse2 sse41 avx avx2 avx512f + vsx vsx2 vsx3 + neon neon_fp16 asimdhp asimddp + */ + """, + baseline="avx vsx2 asimd", + x86="avx512f avx2", armhf="asimddp asimdhp", ppc64="vsx3" + ) + # test skipping non-dispatch features + self.expect_targets( + """ + /*@targets + sse41 avx avx2 avx512f + vsx2 vsx3 + asimd asimdhp asimddp + */ + """, + baseline="", dispatch="sse41 avx2 vsx2 asimd asimddp", + x86="avx2 sse41", armhf="asimddp asimd", ppc64="vsx2" + ) + # test skipping features that not supported + self.expect_targets( + """ + /*@targets + sse2 sse41 avx2 avx512f + vsx2 vsx3 + neon asimdhp asimddp + */ + """, + baseline="", + trap_files=".*(avx2|avx512f|vsx3|asimddp).c", + x86="sse41 sse2", ppc64="vsx2", armhf="asimdhp neon" + ) + # test skipping features that implies each other + self.expect_targets( + """ + /*@targets + sse sse2 avx fma3 avx2 avx512f avx512cd + vsx vsx2 vsx3 + neon neon_vfpv4 neon_fp16 neon_fp16 asimd asimdhp + asimddp asimdfhm + */ + """, + baseline="", + x86_gcc="avx512cd avx512f avx2 fma3 avx sse2", + x86_msvc="avx512cd avx2 avx sse2", + x86_icc="avx512cd avx2 avx sse2", + x86_iccw="avx512cd avx2 avx sse2", + ppc64="vsx3 vsx2 vsx", + ppc64le="vsx3 vsx2", + armhf="asimdfhm asimddp asimdhp asimd neon_vfpv4 neon_fp16 neon", + aarch64="asimdfhm asimddp asimdhp asimd" + ) + + def test_targets_policies(self): + # 'keep_baseline', generate objects for baseline features + self.expect_targets( + """ + /*@targets + $keep_baseline + sse2 sse42 avx2 avx512f + vsx2 vsx3 + neon neon_vfpv4 asimd asimddp + */ + """, + baseline="sse41 avx2 vsx2 asimd vsx3", + x86="avx512f avx2 sse42 sse2", + ppc64="vsx3 vsx2", + armhf="asimddp asimd neon_vfpv4 neon", + # neon, neon_vfpv4, asimd implies each other + aarch64="asimddp asimd" + ) + # 'keep_sort', leave the sort as-is + self.expect_targets( + """ + /*@targets + $keep_baseline $keep_sort + avx512f sse42 avx2 sse2 + vsx2 vsx3 + asimd neon neon_vfpv4 asimddp + */ + """, + x86="avx512f sse42 avx2 sse2", + ppc64="vsx2 vsx3", + armhf="asimd neon neon_vfpv4 asimddp", + # neon, neon_vfpv4, asimd implies each other + aarch64="asimd asimddp" + ) + # 'autovec', skipping features that can't be + # vectorized by the compiler + self.expect_targets( + """ + /*@targets + $keep_baseline $keep_sort $autovec + avx512f avx2 sse42 sse41 sse2 + vsx3 vsx2 + asimddp asimd neon_vfpv4 neon + */ + """, + x86_gcc="avx512f avx2 sse42 sse41 sse2", + x86_icc="avx512f avx2 sse42 sse41 sse2", + x86_iccw="avx512f avx2 sse42 sse41 sse2", + x86_msvc="avx512f avx2 sse2", + ppc64="vsx3 vsx2", + armhf="asimddp asimd neon_vfpv4 neon", + # neon, neon_vfpv4, asimd implies each other + aarch64="asimddp asimd" + ) + for policy in ("$maxopt", "$autovec"): + # 'maxopt' and autovec set the max acceptable optimization flags + self.expect_target_flags( + "/*@targets baseline %s */" % policy, + gcc={"baseline":".*-O3.*"}, icc={"baseline":".*-O3.*"}, + iccw={"baseline":".*/O3.*"}, msvc={"baseline":".*/O2.*"}, + unknown={"baseline":".*"} + ) + + # 'werror', force compilers to treat warnings as errors + self.expect_target_flags( + "/*@targets baseline $werror */", + gcc={"baseline":".*-Werror.*"}, icc={"baseline":".*-Werror.*"}, + iccw={"baseline":".*/Werror.*"}, msvc={"baseline":".*/WX.*"}, + unknown={"baseline":".*"} + ) + + def test_targets_groups(self): + self.expect_targets( + """ + /*@targets $keep_baseline baseline #test_group */ + """, + groups=dict( + test_group=(""" + $keep_baseline + asimddp sse2 vsx2 avx2 vsx3 + avx512f asimdhp + """) + ), + x86="avx512f avx2 sse2 baseline", + ppc64="vsx3 vsx2 baseline", + armhf="asimddp asimdhp baseline" + ) + # test skip duplicating and sorting + self.expect_targets( + """ + /*@targets + * sse42 avx avx512f + * #test_group_1 + * vsx2 + * #test_group_2 + * asimddp asimdfhm + */ + """, + groups=dict( + test_group_1=(""" + VSX2 vsx3 asimd avx2 SSE41 + """), + test_group_2=(""" + vsx2 vsx3 asImd aVx2 sse41 + """) + ), + x86="avx512f avx2 avx sse42 sse41", + ppc64="vsx3 vsx2", + # vsx2 part of the default baseline of ppc64le, option ("min") + ppc64le="vsx3", + armhf="asimdfhm asimddp asimd", + # asimd part of the default baseline of aarch64, option ("min") + aarch64="asimdfhm asimddp" + ) + + def test_targets_multi(self): + self.expect_targets( + """ + /*@targets + (avx512_clx avx512_cnl) (asimdhp asimddp) + */ + """, + x86=r"\(avx512_clx avx512_cnl\)", + armhf=r"\(asimdhp asimddp\)", + ) + # test skipping implied features and auto-sort + self.expect_targets( + """ + /*@targets + f16c (sse41 avx sse42) (sse3 avx2 avx512f) + vsx2 (vsx vsx3 vsx2) + (neon neon_vfpv4 asimd asimdhp asimddp) + */ + """, + x86="avx512f f16c avx", + ppc64="vsx3 vsx2", + ppc64le="vsx3", # vsx2 part of baseline + armhf=r"\(asimdhp asimddp\)", + ) + # test skipping implied features and keep sort + self.expect_targets( + """ + /*@targets $keep_sort + (sse41 avx sse42) (sse3 avx2 avx512f) + (vsx vsx3 vsx2) + (asimddp neon neon_vfpv4 asimd asimdhp) + */ + """, + x86="avx avx512f", + ppc64="vsx3", + armhf=r"\(asimdhp asimddp\)", + ) + # test compiler variety and avoiding duplicating + self.expect_targets( + """ + /*@targets $keep_sort + fma3 avx2 (fma3 avx2) (avx2 fma3) avx2 fma3 + */ + """, + x86_gcc=r"fma3 avx2 \(fma3 avx2\)", + x86_icc="avx2", x86_iccw="avx2", + x86_msvc="avx2" + ) + +def new_test(arch, cc): + if is_standalone: return textwrap.dedent("""\ + class TestCCompilerOpt_{class_name}(_Test_CCompilerOpt, unittest.TestCase): + arch = '{arch}' + cc = '{cc}' + def __init__(self, methodName="runTest"): + unittest.TestCase.__init__(self, methodName) + self.setup() + """).format( + class_name=arch + '_' + cc, arch=arch, cc=cc + ) + return textwrap.dedent("""\ + class TestCCompilerOpt_{class_name}(_Test_CCompilerOpt): + arch = '{arch}' + cc = '{cc}' + """).format( + class_name=arch + '_' + cc, arch=arch, cc=cc + ) +""" +if 1 and is_standalone: + FakeCCompilerOpt.fake_info = "x86_icc" + cco = FakeCCompilerOpt(None, cpu_baseline="avx2") + print(' '.join(cco.cpu_baseline_names())) + print(cco.cpu_baseline_flags()) + unittest.main() + sys.exit() +""" +for arch, compilers in arch_compilers.items(): + for cc in compilers: + exec(new_test(arch, cc)) + +if is_standalone: + unittest.main() diff --git a/numpy/distutils/tests/test_ccompiler_opt_conf.py b/numpy/distutils/tests/test_ccompiler_opt_conf.py new file mode 100644 index 000000000..2f83a59e0 --- /dev/null +++ b/numpy/distutils/tests/test_ccompiler_opt_conf.py @@ -0,0 +1,169 @@ +import unittest +from os import sys, path + +is_standalone = __name__ == '__main__' and __package__ is None +if is_standalone: + sys.path.append(path.abspath(path.join(path.dirname(__file__), ".."))) + from ccompiler_opt import CCompilerOpt +else: + from numpy.distutils.ccompiler_opt import CCompilerOpt + +arch_compilers = dict( + x86 = ("gcc", "clang", "icc", "iccw", "msvc"), + x64 = ("gcc", "clang", "icc", "iccw", "msvc"), + ppc64 = ("gcc", "clang"), + ppc64le = ("gcc", "clang"), + armhf = ("gcc", "clang"), + aarch64 = ("gcc", "clang"), + narch = ("gcc",) +) + +class FakeCCompilerOpt(CCompilerOpt): + fake_info = "" + def __init__(self, *args, **kwargs): + CCompilerOpt.__init__(self, None, **kwargs) + def dist_compile(self, sources, flags, **kwargs): + return sources + def dist_info(self): + return FakeCCompilerOpt.fake_info + @staticmethod + def dist_log(*args, stderr=False): + pass + +class _TestConfFeatures(FakeCCompilerOpt): + """A hook to check the sanity of configured features +- before it called by the abstract class '_Feature' + """ + + def conf_features_partial(self): + conf_all = self.conf_features + for feature_name, feature in conf_all.items(): + self.test_feature( + "attribute conf_features", + conf_all, feature_name, feature + ) + + conf_partial = FakeCCompilerOpt.conf_features_partial(self) + for feature_name, feature in conf_partial.items(): + self.test_feature( + "conf_features_partial()", + conf_partial, feature_name, feature + ) + return conf_partial + + def test_feature(self, log, search_in, feature_name, feature_dict): + error_msg = ( + "during validate '{}' within feature '{}', " + "march '{}' and compiler '{}'\n>> " + ).format(log, feature_name, self.cc_march, self.cc_name) + + if not feature_name.isupper(): + raise AssertionError(error_msg + "feature name must be in uppercase") + + for option, val in feature_dict.items(): + self.test_option_types(error_msg, option, val) + self.test_duplicates(error_msg, option, val) + + self.test_implies(error_msg, search_in, feature_name, feature_dict) + self.test_group(error_msg, search_in, feature_name, feature_dict) + + def test_option_types(self, error_msg, option, val): + for tp, available in ( + ((str, list), ( + "implies", "headers", "flags", "group", "detect" + )), + ((str,), ("disable",)), + ((int,), ("interest",)), + ((bool,), ("implies_detect",)), + ((bool, type(None)), ("autovec",)), + ) : + found_it = option in available + if not found_it: + continue + if not isinstance(val, tp): + error_tp = [t.__name__ for t in (*tp,)] + error_tp = ' or '.join(error_tp) + raise AssertionError(error_msg + \ + "expected '%s' type for option '%s' not '%s'" % ( + error_tp, option, type(val).__name__ + )) + break + + if not found_it: + raise AssertionError(error_msg + \ + "invalid option name '%s'" % option + ) + + def test_duplicates(self, error_msg, option, val): + if option not in ( + "implies", "headers", "flags", "group", "detect" + ) : return + + if isinstance(val, str): + val = val.split() + + if len(val) != len(set(val)): + raise AssertionError(error_msg + \ + "duplicated values in option '%s'" % option + ) + + def test_implies(self, error_msg, search_in, feature_name, feature_dict): + if feature_dict.get("disabled") is not None: + return + implies = feature_dict.get("implies", "") + if not implies: + return + if isinstance(implies, str): + implies = implies.split() + + if feature_name in implies: + raise AssertionError(error_msg + \ + "feature implies itself" + ) + + for impl in implies: + impl_dict = search_in.get(impl) + if impl_dict is not None: + if "disable" in impl_dict: + raise AssertionError(error_msg + \ + "implies disabled feature '%s'" % impl + ) + continue + raise AssertionError(error_msg + \ + "implies non-exist feature '%s'" % impl + ) + + def test_group(self, error_msg, search_in, feature_name, feature_dict): + if feature_dict.get("disabled") is not None: + return + group = feature_dict.get("group", "") + if not group: + return + if isinstance(group, str): + group = group.split() + + for f in group: + impl_dict = search_in.get(f) + if not impl_dict or "disable" in impl_dict: + continue + raise AssertionError(error_msg + \ + "in option '%s', '%s' already exists as a feature name" % ( + option, f + )) + +class TestConfFeatures(unittest.TestCase): + def __init__(self, methodName="runTest"): + unittest.TestCase.__init__(self, methodName) + self.setup() + + def setup(self): + FakeCCompilerOpt.conf_nocache = True + + def test_features(self): + for arch, compilers in arch_compilers.items(): + for cc in compilers: + FakeCCompilerOpt.fake_info = arch + cc + _TestConfFeatures() + +if is_standalone: + unittest.main() diff --git a/numpy/doc/broadcasting.py b/numpy/doc/broadcasting.py index 63975e6a9..4ac1fd129 100644 --- a/numpy/doc/broadcasting.py +++ b/numpy/doc/broadcasting.py @@ -52,8 +52,8 @@ because broadcasting moves less memory around during the multiplication General Broadcasting Rules ========================== When operating on two arrays, NumPy compares their shapes element-wise. -It starts with the trailing dimensions and works its way forward. Two -dimensions are compatible when +It starts with the trailing (i.e. rightmost) dimensions and works its +way left. Two dimensions are compatible when 1) they are equal, or 2) one of them is 1 diff --git a/numpy/f2py/cb_rules.py b/numpy/f2py/cb_rules.py index 5c0668db1..3068dc897 100644 --- a/numpy/f2py/cb_rules.py +++ b/numpy/f2py/cb_rules.py @@ -33,13 +33,45 @@ cb_routine_rules = { 'cbtypedefs': 'typedef #rctype#(*#name#_typedef)(#optargs_td##args_td##strarglens_td##noargs#);', 'body': """ #begintitle# -PyObject *#name#_capi = NULL;/*was Py_None*/ -PyTupleObject *#name#_args_capi = NULL; -int #name#_nofargs = 0; -jmp_buf #name#_jmpbuf; +typedef struct { + PyObject *capi; + PyTupleObject *args_capi; + int nofargs; + jmp_buf jmpbuf; +} #name#_t; + +#if defined(F2PY_THREAD_LOCAL_DECL) && !defined(F2PY_USE_PYTHON_TLS) + +static F2PY_THREAD_LOCAL_DECL #name#_t *_active_#name# = NULL; + +static #name#_t *swap_active_#name#(#name#_t *ptr) { + #name#_t *prev = _active_#name#; + _active_#name# = ptr; + return prev; +} + +static #name#_t *get_active_#name#(void) { + return _active_#name#; +} + +#else + +static #name#_t *swap_active_#name#(#name#_t *ptr) { + char *key = "__f2py_cb_#name#"; + return (#name#_t *)F2PySwapThreadLocalCallbackPtr(key, ptr); +} + +static #name#_t *get_active_#name#(void) { + char *key = "__f2py_cb_#name#"; + return (#name#_t *)F2PyGetThreadLocalCallbackPtr(key); +} + +#endif + /*typedef #rctype#(*#name#_typedef)(#optargs_td##args_td##strarglens_td##noargs#);*/ #static# #rctype# #callbackname# (#optargs##args##strarglens##noargs#) { - PyTupleObject *capi_arglist = #name#_args_capi; + #name#_t *cb; + PyTupleObject *capi_arglist = NULL; PyObject *capi_return = NULL; PyObject *capi_tmp = NULL; PyObject *capi_arglist_list = NULL; @@ -49,19 +81,21 @@ jmp_buf #name#_jmpbuf; #ifdef F2PY_REPORT_ATEXIT f2py_cb_start_clock(); #endif + cb = get_active_#name#(); + capi_arglist = cb->args_capi; CFUNCSMESS(\"cb:Call-back function #name# (maxnofargs=#maxnofargs#(-#nofoptargs#))\\n\"); - CFUNCSMESSPY(\"cb:#name#_capi=\",#name#_capi); - if (#name#_capi==NULL) { + CFUNCSMESSPY(\"cb:#name#_capi=\",cb->capi); + if (cb->capi==NULL) { capi_longjmp_ok = 0; - #name#_capi = PyObject_GetAttrString(#modulename#_module,\"#argname#\"); + cb->capi = PyObject_GetAttrString(#modulename#_module,\"#argname#\"); } - if (#name#_capi==NULL) { + if (cb->capi==NULL) { PyErr_SetString(#modulename#_error,\"cb: Callback #argname# not defined (as an argument or module #modulename# attribute).\\n\"); goto capi_fail; } - if (F2PyCapsule_Check(#name#_capi)) { + if (F2PyCapsule_Check(cb->capi)) { #name#_typedef #name#_cptr; - #name#_cptr = F2PyCapsule_AsVoidPtr(#name#_capi); + #name#_cptr = F2PyCapsule_AsVoidPtr(cb->capi); #returncptr#(*#name#_cptr)(#optargs_nm##args_nm##strarglens_nm#); #return# } @@ -103,11 +137,11 @@ f2py_cb_start_clock(); f2py_cb_start_call_clock(); #endif #ifdef PYPY_VERSION - capi_return = PyObject_CallObject(#name#_capi,(PyObject *)capi_arglist_list); + capi_return = PyObject_CallObject(cb->capi,(PyObject *)capi_arglist_list); Py_DECREF(capi_arglist_list); capi_arglist_list = NULL; #else - capi_return = PyObject_CallObject(#name#_capi,(PyObject *)capi_arglist); + capi_return = PyObject_CallObject(cb->capi,(PyObject *)capi_arglist); #endif #ifdef F2PY_REPORT_ATEXIT f2py_cb_stop_call_clock(); @@ -137,15 +171,16 @@ capi_fail: fprintf(stderr,\"Call-back #name# failed.\\n\"); Py_XDECREF(capi_return); Py_XDECREF(capi_arglist_list); - if (capi_longjmp_ok) - longjmp(#name#_jmpbuf,-1); + if (capi_longjmp_ok) { + longjmp(cb->jmpbuf,-1); + } capi_return_pt: ; #return# } #endtitle# """, - 'need': ['setjmp.h', 'CFUNCSMESS'], + 'need': ['setjmp.h', 'CFUNCSMESS', 'F2PY_THREAD_LOCAL_DECL'], 'maxnofargs': '#maxnofargs#', 'nofoptargs': '#nofoptargs#', 'docstr': """\ @@ -335,11 +370,11 @@ cb_arg_rules = [ '_check': isscalar }, { 'pyobjfrom': [{isintent_in: """\ - if (#name#_nofargs>capi_i) + if (cb->nofargs>capi_i) if (CAPI_ARGLIST_SETITEM(capi_i++,pyobj_from_#ctype#1(#varname_i#))) goto capi_fail;"""}, {isintent_inout: """\ - if (#name#_nofargs>capi_i) + if (cb->nofargs>capi_i) if (CAPI_ARGLIST_SETITEM(capi_i++,pyarr_from_p_#ctype#1(#varname_i#_cb_capi))) goto capi_fail;"""}], 'need': [{isintent_in: 'pyobj_from_#ctype#1'}, @@ -360,11 +395,11 @@ cb_arg_rules = [ }, { 'pyobjfrom': [{debugcapi: ' fprintf(stderr,"debug-capi:cb:#varname#=\\"#showvalueformat#\\":%d:\\n",#varname_i#,#varname_i#_cb_len);'}, {isintent_in: """\ - if (#name#_nofargs>capi_i) + if (cb->nofargs>capi_i) if (CAPI_ARGLIST_SETITEM(capi_i++,pyobj_from_#ctype#1size(#varname_i#,#varname_i#_cb_len))) goto capi_fail;"""}, {isintent_inout: """\ - if (#name#_nofargs>capi_i) { + if (cb->nofargs>capi_i) { int #varname_i#_cb_dims[] = {#varname_i#_cb_len}; if (CAPI_ARGLIST_SETITEM(capi_i++,pyarr_from_p_#ctype#1(#varname_i#,#varname_i#_cb_dims))) goto capi_fail; @@ -384,13 +419,13 @@ cb_arg_rules = [ { 'pyobjfrom': [{debugcapi: ' fprintf(stderr,"debug-capi:cb:#varname#\\n");'}, {isintent_c: """\ - if (#name#_nofargs>capi_i) { + if (cb->nofargs>capi_i) { int itemsize_ = #atype# == NPY_STRING ? 1 : 0; /*XXX: Hmm, what will destroy this array??? */ PyArrayObject *tmp_arr = (PyArrayObject *)PyArray_New(&PyArray_Type,#rank#,#varname_i#_Dims,#atype#,NULL,(char*)#varname_i#,itemsize_,NPY_ARRAY_CARRAY,NULL); """, l_not(isintent_c): """\ - if (#name#_nofargs>capi_i) { + if (cb->nofargs>capi_i) { int itemsize_ = #atype# == NPY_STRING ? 1 : 0; /*XXX: Hmm, what will destroy this array??? */ PyArrayObject *tmp_arr = (PyArrayObject *)PyArray_New(&PyArray_Type,#rank#,#varname_i#_Dims,#atype#,NULL,(char*)#varname_i#,itemsize_,NPY_ARRAY_FARRAY,NULL); diff --git a/numpy/f2py/cfuncs.py b/numpy/f2py/cfuncs.py index f1ac214d4..ccbc9b0fb 100644 --- a/numpy/f2py/cfuncs.py +++ b/numpy/f2py/cfuncs.py @@ -543,6 +543,21 @@ cppmacros['OLDPYNUM'] = """\ #error You need to install NumPy version 0.13 or higher. See https://scipy.org/install.html #endif """ +cppmacros["F2PY_THREAD_LOCAL_DECL"] = """\ +#ifndef F2PY_THREAD_LOCAL_DECL +#if defined(_MSC_VER) +#define F2PY_THREAD_LOCAL_DECL __declspec(thread) +#elif defined(__STDC_VERSION__) \\ + && (__STDC_VERSION__ >= 201112L) \\ + && !defined(__STDC_NO_THREADS__) +#include <threads.h> +#define F2PY_THREAD_LOCAL_DECL thread_local +#elif defined(__GNUC__) \\ + && (__GNUC__ > 4 || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 4))) +#define F2PY_THREAD_LOCAL_DECL __thread +#endif +#endif +""" ################# C functions ############### cfuncs['calcarrindex'] = """\ diff --git a/numpy/f2py/rules.py b/numpy/f2py/rules.py index ad49f4590..56f2033ff 100755 --- a/numpy/f2py/rules.py +++ b/numpy/f2py/rules.py @@ -55,6 +55,9 @@ __version__ = "$Revision: 1.129 $"[10:-1] from . import __version__ f2py_version = __version__.version +from .. import version as _numpy_version +numpy_version = _numpy_version.version + import os import time import copy @@ -206,6 +209,9 @@ PyMODINIT_FUNC PyInit_#modulename#(void) { \t\t\"This module '#modulename#' is auto-generated with f2py (version:#f2py_version#).\\nFunctions:\\n\"\n#docs#\".\"); \tPyDict_SetItemString(d, \"__doc__\", s); \tPy_DECREF(s); +\ts = PyUnicode_FromString(\"""" + numpy_version + """\"); +\tPyDict_SetItemString(d, \"__f2py_numpy_version__\", s); +\tPy_DECREF(s); \t#modulename#_error = PyErr_NewException (\"#modulename#.error\", NULL, NULL); \t/* \t * Store the error object inside the dict, so that it could get deallocated. @@ -749,10 +755,9 @@ arg_rules = [ 'docstrcbs': '#cbdocstr#', 'latexdocstrcbs': '\\item[] #cblatexdocstr#', 'latexdocstropt': {isintent_nothide: '\\item[]{{}\\verb@#varname#_extra_args := () input tuple@{}} --- Extra arguments for call-back function {{}\\verb@#varname#@{}}.'}, - 'decl': [' PyObject *#varname#_capi = Py_None;', + 'decl': [' #cbname#_t #varname#_cb = { Py_None, NULL, 0 };', + ' #cbname#_t *#varname#_cb_ptr = &#varname#_cb;', ' PyTupleObject *#varname#_xa_capi = NULL;', - ' PyTupleObject *#varname#_args_capi = NULL;', - ' int #varname#_nofargs_capi = 0;', {l_not(isintent_callback): ' #cbname#_typedef #varname#_cptr;'} ], @@ -760,25 +765,25 @@ arg_rules = [ 'argformat': {isrequired: 'O'}, 'keyformat': {isoptional: 'O'}, 'xaformat': {isintent_nothide: 'O!'}, - 'args_capi': {isrequired: ',&#varname#_capi'}, - 'keys_capi': {isoptional: ',&#varname#_capi'}, + 'args_capi': {isrequired: ',&#varname#_cb.capi'}, + 'keys_capi': {isoptional: ',&#varname#_cb.capi'}, 'keys_xa': ',&PyTuple_Type,&#varname#_xa_capi', - 'setjmpbuf': '(setjmp(#cbname#_jmpbuf))', + 'setjmpbuf': '(setjmp(#varname#_cb.jmpbuf))', 'callfortran': {l_not(isintent_callback): '#varname#_cptr,'}, 'need': ['#cbname#', 'setjmp.h'], '_check':isexternal }, { 'frompyobj': [{l_not(isintent_callback): """\ -if(F2PyCapsule_Check(#varname#_capi)) { - #varname#_cptr = F2PyCapsule_AsVoidPtr(#varname#_capi); +if(F2PyCapsule_Check(#varname#_cb.capi)) { + #varname#_cptr = F2PyCapsule_AsVoidPtr(#varname#_cb.capi); } else { #varname#_cptr = #cbname#; } """}, {isintent_callback: """\ -if (#varname#_capi==Py_None) { - #varname#_capi = PyObject_GetAttrString(#modulename#_module,\"#varname#\"); - if (#varname#_capi) { +if (#varname#_cb.capi==Py_None) { + #varname#_cb.capi = PyObject_GetAttrString(#modulename#_module,\"#varname#\"); + if (#varname#_cb.capi) { if (#varname#_xa_capi==NULL) { if (PyObject_HasAttrString(#modulename#_module,\"#varname#_extra_args\")) { PyObject* capi_tmp = PyObject_GetAttrString(#modulename#_module,\"#varname#_extra_args\"); @@ -796,34 +801,28 @@ if (#varname#_capi==Py_None) { } } } - if (#varname#_capi==NULL) { + if (#varname#_cb.capi==NULL) { PyErr_SetString(#modulename#_error,\"Callback #varname# not defined (as an argument or module #modulename# attribute).\\n\"); return NULL; } } """}, """\ - #varname#_nofargs_capi = #cbname#_nofargs; - if (create_cb_arglist(#varname#_capi,#varname#_xa_capi,#maxnofargs#,#nofoptargs#,&#cbname#_nofargs,&#varname#_args_capi,\"failed in processing argument list for call-back #varname#.\")) { - jmp_buf #varname#_jmpbuf;""", + if (create_cb_arglist(#varname#_cb.capi,#varname#_xa_capi,#maxnofargs#,#nofoptargs#,&#varname#_cb.nofargs,&#varname#_cb.args_capi,\"failed in processing argument list for call-back #varname#.\")) { +""", {debugcapi: ["""\ - fprintf(stderr,\"debug-capi:Assuming %d arguments; at most #maxnofargs#(-#nofoptargs#) is expected.\\n\",#cbname#_nofargs); + fprintf(stderr,\"debug-capi:Assuming %d arguments; at most #maxnofargs#(-#nofoptargs#) is expected.\\n\",#varname#_cb.nofargs); CFUNCSMESSPY(\"for #varname#=\",#cbname#_capi);""", {l_not(isintent_callback): """ fprintf(stderr,\"#vardebugshowvalue# (call-back in C).\\n\",#cbname#);"""}]}, """\ - CFUNCSMESS(\"Saving jmpbuf for `#varname#`.\\n\"); - SWAP(#varname#_capi,#cbname#_capi,PyObject); - SWAP(#varname#_args_capi,#cbname#_args_capi,PyTupleObject); - memcpy(&#varname#_jmpbuf,&#cbname#_jmpbuf,sizeof(jmp_buf));""", + CFUNCSMESS(\"Saving callback variables for `#varname#`.\\n\"); + #varname#_cb_ptr = swap_active_#cbname#(#varname#_cb_ptr);""", ], 'cleanupfrompyobj': """\ - CFUNCSMESS(\"Restoring jmpbuf for `#varname#`.\\n\"); - #cbname#_capi = #varname#_capi; - Py_DECREF(#cbname#_args_capi); - #cbname#_args_capi = #varname#_args_capi; - #cbname#_nofargs = #varname#_nofargs_capi; - memcpy(&#cbname#_jmpbuf,&#varname#_jmpbuf,sizeof(jmp_buf)); + CFUNCSMESS(\"Restoring callback variables for `#varname#`.\\n\"); + #varname#_cb_ptr = swap_active_#cbname#(#varname#_cb_ptr); + Py_DECREF(#varname#_cb.args_capi); }""", 'need': ['SWAP', 'create_cb_arglist'], '_check':isexternal, diff --git a/numpy/f2py/src/fortranobject.c b/numpy/f2py/src/fortranobject.c index b3a04bcf0..aa46c57d0 100644 --- a/numpy/f2py/src/fortranobject.c +++ b/numpy/f2py/src/fortranobject.c @@ -30,6 +30,68 @@ F2PyDict_SetItemString(PyObject *dict, char *name, PyObject *obj) return PyDict_SetItemString(dict, name, obj); } +/* + * Python-only fallback for thread-local callback pointers + */ +void *F2PySwapThreadLocalCallbackPtr(char *key, void *ptr) +{ + PyObject *local_dict, *value; + void *prev; + + local_dict = PyThreadState_GetDict(); + if (local_dict == NULL) { + Py_FatalError("F2PySwapThreadLocalCallbackPtr: PyThreadState_GetDict failed"); + } + + value = PyDict_GetItemString(local_dict, key); + if (value != NULL) { + prev = PyLong_AsVoidPtr(value); + if (PyErr_Occurred()) { + Py_FatalError("F2PySwapThreadLocalCallbackPtr: PyLong_AsVoidPtr failed"); + } + } + else { + prev = NULL; + } + + value = PyLong_FromVoidPtr((void *)ptr); + if (value == NULL) { + Py_FatalError("F2PySwapThreadLocalCallbackPtr: PyLong_FromVoidPtr failed"); + } + + if (PyDict_SetItemString(local_dict, key, value) != 0) { + Py_FatalError("F2PySwapThreadLocalCallbackPtr: PyDict_SetItemString failed"); + } + + Py_DECREF(value); + + return prev; +} + +void *F2PyGetThreadLocalCallbackPtr(char *key) +{ + PyObject *local_dict, *value; + void *prev; + + local_dict = PyThreadState_GetDict(); + if (local_dict == NULL) { + Py_FatalError("F2PyGetThreadLocalCallbackPtr: PyThreadState_GetDict failed"); + } + + value = PyDict_GetItemString(local_dict, key); + if (value != NULL) { + prev = PyLong_AsVoidPtr(value); + if (PyErr_Occurred()) { + Py_FatalError("F2PyGetThreadLocalCallbackPtr: PyLong_AsVoidPtr failed"); + } + } + else { + prev = NULL; + } + + return prev; +} + /************************* FortranObject *******************************/ typedef PyObject *(*fortranfunc)(PyObject *,PyObject *,PyObject *,void *); diff --git a/numpy/f2py/src/fortranobject.h b/numpy/f2py/src/fortranobject.h index 5c382ab7b..d4cc10243 100644 --- a/numpy/f2py/src/fortranobject.h +++ b/numpy/f2py/src/fortranobject.h @@ -86,6 +86,9 @@ PyObject * F2PyCapsule_FromVoidPtr(void *ptr, void (*dtor)(PyObject *)); void * F2PyCapsule_AsVoidPtr(PyObject *obj); int F2PyCapsule_Check(PyObject *ptr); +extern void *F2PySwapThreadLocalCallbackPtr(char *key, void *ptr); +extern void *F2PyGetThreadLocalCallbackPtr(char *key); + #define ISCONTIGUOUS(m) (PyArray_FLAGS(m) & NPY_ARRAY_C_CONTIGUOUS) #define F2PY_INTENT_IN 1 #define F2PY_INTENT_INOUT 2 diff --git a/numpy/f2py/tests/test_callback.py b/numpy/f2py/tests/test_callback.py index 4e29ab9fc..81650a819 100644 --- a/numpy/f2py/tests/test_callback.py +++ b/numpy/f2py/tests/test_callback.py @@ -2,6 +2,10 @@ import math import textwrap import sys import pytest +import threading +import traceback +import time +import random import numpy as np from numpy.testing import assert_, assert_equal, IS_PYPY @@ -161,3 +165,49 @@ cf2py intent(out) a f = getattr(self.module, 'string_callback_array') res = f(callback, cu, len(cu)) assert_(res == 0, repr(res)) + + def test_threadsafety(self): + # Segfaults if the callback handling is not threadsafe + + errors = [] + + def cb(): + # Sleep here to make it more likely for another thread + # to call their callback at the same time. + time.sleep(1e-3) + + # Check reentrancy + r = self.module.t(lambda: 123) + assert_(r == 123) + + return 42 + + def runner(name): + try: + for j in range(50): + r = self.module.t(cb) + assert_(r == 42) + self.check_function(name) + except Exception: + errors.append(traceback.format_exc()) + + threads = [threading.Thread(target=runner, args=(arg,)) + for arg in ("t", "t2") for n in range(20)] + + for t in threads: + t.start() + + for t in threads: + t.join() + + errors = "\n\n".join(errors) + if errors: + raise AssertionError(errors) + + +class TestF77CallbackPythonTLS(TestF77Callback): + """ + Callback tests using Python thread-local storage instead of + compiler-provided + """ + options = ["-DF2PY_USE_PYTHON_TLS"] diff --git a/numpy/f2py/tests/test_regression.py b/numpy/f2py/tests/test_regression.py index 67e00f1f7..a1b772069 100644 --- a/numpy/f2py/tests/test_regression.py +++ b/numpy/f2py/tests/test_regression.py @@ -2,7 +2,7 @@ import os import pytest import numpy as np -from numpy.testing import assert_raises, assert_equal +from numpy.testing import assert_, assert_raises, assert_equal, assert_string_equal from . import util @@ -25,3 +25,23 @@ class TestIntentInOut(util.F2PyTest): x = np.arange(3, dtype=np.float32) self.module.foo(x) assert_equal(x, [3, 1, 2]) + + +class TestNumpyVersionAttribute(util.F2PyTest): + # Check that th attribute __f2py_numpy_version__ is present + # in the compiled module and that has the value np.__version__. + sources = [_path('src', 'regression', 'inout.f90')] + + @pytest.mark.slow + def test_numpy_version_attribute(self): + + # Check that self.module has an attribute named "__f2py_numpy_version__" + assert_(hasattr(self.module, "__f2py_numpy_version__"), + msg="Fortran module does not have __f2py_numpy_version__") + + # Check that the attribute __f2py_numpy_version__ is a string + assert_(isinstance(self.module.__f2py_numpy_version__, str), + msg="__f2py_numpy_version__ is not a string") + + # Check that __f2py_numpy_version__ has the value numpy.__version__ + assert_string_equal(np.__version__, self.module.__f2py_numpy_version__) diff --git a/numpy/fft/__init__.py b/numpy/fft/__init__.py index 36cfe81b3..af0859def 100644 --- a/numpy/fft/__init__.py +++ b/numpy/fft/__init__.py @@ -128,11 +128,17 @@ promote input arrays, see `scipy.fftpack`. Normalization ------------- -The default normalization has the direct transforms unscaled and the inverse -transforms are scaled by :math:`1/n`. It is possible to obtain unitary -transforms by setting the keyword argument ``norm`` to ``"ortho"`` (default is -`None`) so that both direct and inverse transforms will be scaled by -:math:`1/\\sqrt{n}`. +The argument ``norm`` indicates which direction of the pair of direct/inverse +transforms is scaled and with what normalization factor. +The default normalization (``"backward"``) has the direct (forward) transforms +unscaled and the inverse (backward) transforms scaled by :math:`1/n`. It is +possible to obtain unitary transforms by setting the keyword argument ``norm`` +to ``"ortho"`` so that both direct and inverse transforms are scaled by +:math:`1/\\sqrt{n}`. Finally, setting the keyword argument ``norm`` to +``"forward"`` has the direct transforms scaled by :math:`1/n` and the inverse +transforms unscaled (i.e. exactly opposite to the default ``"backward"``). +`None` is an alias of the default option ``"backward"`` for backward +compatibility. Real and Hermitian transforms ----------------------------- diff --git a/numpy/fft/_pocketfft.py b/numpy/fft/_pocketfft.py index e9f554fe7..38ea69834 100644 --- a/numpy/fft/_pocketfft.py +++ b/numpy/fft/_pocketfft.py @@ -3,20 +3,20 @@ Discrete Fourier Transforms Routines in this module: -fft(a, n=None, axis=-1) -ifft(a, n=None, axis=-1) -rfft(a, n=None, axis=-1) -irfft(a, n=None, axis=-1) -hfft(a, n=None, axis=-1) -ihfft(a, n=None, axis=-1) -fftn(a, s=None, axes=None) -ifftn(a, s=None, axes=None) -rfftn(a, s=None, axes=None) -irfftn(a, s=None, axes=None) -fft2(a, s=None, axes=(-2,-1)) -ifft2(a, s=None, axes=(-2, -1)) -rfft2(a, s=None, axes=(-2,-1)) -irfft2(a, s=None, axes=(-2, -1)) +fft(a, n=None, axis=-1, norm="backward") +ifft(a, n=None, axis=-1, norm="backward") +rfft(a, n=None, axis=-1, norm="backward") +irfft(a, n=None, axis=-1, norm="backward") +hfft(a, n=None, axis=-1, norm="backward") +ihfft(a, n=None, axis=-1, norm="backward") +fftn(a, s=None, axes=None, norm="backward") +ifftn(a, s=None, axes=None, norm="backward") +rfftn(a, s=None, axes=None, norm="backward") +irfftn(a, s=None, axes=None, norm="backward") +fft2(a, s=None, axes=(-2,-1), norm="backward") +ifft2(a, s=None, axes=(-2, -1), norm="backward") +rfft2(a, s=None, axes=(-2,-1), norm="backward") +irfft2(a, s=None, axes=(-2, -1), norm="backward") i = inverse transform r = transform of purely real data @@ -51,10 +51,6 @@ def _raw_fft(a, n, axis, is_real, is_forward, inv_norm): if n is None: n = a.shape[axis] - if n < 1: - raise ValueError("Invalid number of FFT data points (%d) specified." - % n) - fct = 1/inv_norm if a.shape[axis] != n: @@ -79,13 +75,44 @@ def _raw_fft(a, n, axis, is_real, is_forward, inv_norm): return r -def _unitary(norm): - if norm is None: - return False - if norm=="ortho": - return True - raise ValueError("Invalid norm value %s, should be None or \"ortho\"." - % norm) +def _get_forward_norm(n, norm): + if n < 1: + raise ValueError(f"Invalid number of FFT data points ({n}) specified.") + + if norm is None or norm == "backward": + return 1 + elif norm == "ortho": + return sqrt(n) + elif norm == "forward": + return n + raise ValueError(f'Invalid norm value {norm}; should be "backward",' + '"ortho" or "forward".') + + +def _get_backward_norm(n, norm): + if n < 1: + raise ValueError(f"Invalid number of FFT data points ({n}) specified.") + + if norm is None or norm == "backward": + return n + elif norm == "ortho": + return sqrt(n) + elif norm == "forward": + return 1 + raise ValueError(f'Invalid norm value {norm}; should be "backward", ' + '"ortho" or "forward".') + + +_SWAP_DIRECTION_MAP = {"backward": "forward", None: "forward", + "ortho": "ortho", "forward": "backward"} + + +def _swap_direction(norm): + try: + return _SWAP_DIRECTION_MAP[norm] + except KeyError: + raise ValueError(f'Invalid norm value {norm}; should be "backward", ' + '"ortho" or "forward".') def _fft_dispatcher(a, n=None, axis=None, norm=None): @@ -113,10 +140,16 @@ def fft(a, n=None, axis=-1, norm=None): axis : int, optional Axis over which to compute the FFT. If not given, the last axis is used. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -175,13 +208,10 @@ def fft(a, n=None, axis=-1, norm=None): >>> plt.show() """ - a = asarray(a) if n is None: n = a.shape[axis] - inv_norm = 1 - if norm is not None and _unitary(norm): - inv_norm = sqrt(n) + inv_norm = _get_forward_norm(n, norm) output = _raw_fft(a, n, axis, False, True, inv_norm) return output @@ -222,10 +252,16 @@ def ifft(a, n=None, axis=-1, norm=None): axis : int, optional Axis over which to compute the inverse DFT. If not given, the last axis is used. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -274,15 +310,11 @@ def ifft(a, n=None, axis=-1, norm=None): a = asarray(a) if n is None: n = a.shape[axis] - if norm is not None and _unitary(norm): - inv_norm = sqrt(max(n, 1)) - else: - inv_norm = n + inv_norm = _get_backward_norm(n, norm) output = _raw_fft(a, n, axis, False, False, inv_norm) return output - @array_function_dispatch(_fft_dispatcher) def rfft(a, n=None, axis=-1, norm=None): """ @@ -304,10 +336,16 @@ def rfft(a, n=None, axis=-1, norm=None): axis : int, optional Axis over which to compute the FFT. If not given, the last axis is used. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -363,11 +401,9 @@ def rfft(a, n=None, axis=-1, norm=None): """ a = asarray(a) - inv_norm = 1 - if norm is not None and _unitary(norm): - if n is None: - n = a.shape[axis] - inv_norm = sqrt(n) + if n is None: + n = a.shape[axis] + inv_norm = _get_forward_norm(n, norm) output = _raw_fft(a, n, axis, True, True, inv_norm) return output @@ -402,10 +438,16 @@ def irfft(a, n=None, axis=-1, norm=None): axis : int, optional Axis over which to compute the inverse FFT. If not given, the last axis is used. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -465,9 +507,7 @@ def irfft(a, n=None, axis=-1, norm=None): a = asarray(a) if n is None: n = (a.shape[axis] - 1) * 2 - inv_norm = n - if norm is not None and _unitary(norm): - inv_norm = sqrt(n) + inv_norm = _get_backward_norm(n, norm) output = _raw_fft(a, n, axis, True, False, inv_norm) return output @@ -492,11 +532,17 @@ def hfft(a, n=None, axis=-1, norm=None): axis : int, optional Axis over which to compute the FFT. If not given, the last axis is used. - norm : {None, "ortho"}, optional - Normalization mode (see `numpy.fft`). Default is None. - + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. + Returns ------- out : ndarray @@ -559,8 +605,9 @@ def hfft(a, n=None, axis=-1, norm=None): a = asarray(a) if n is None: n = (a.shape[axis] - 1) * 2 - unitary = _unitary(norm) - return irfft(conjugate(a), n, axis) * (sqrt(n) if unitary else n) + new_norm = _swap_direction(norm) + output = irfft(conjugate(a), n, axis, norm=new_norm) + return output @array_function_dispatch(_fft_dispatcher) @@ -581,11 +628,17 @@ def ihfft(a, n=None, axis=-1, norm=None): axis : int, optional Axis over which to compute the inverse FFT. If not given, the last axis is used. - norm : {None, "ortho"}, optional - Normalization mode (see `numpy.fft`). Default is None. - + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. + Returns ------- out : complex ndarray @@ -619,9 +672,9 @@ def ihfft(a, n=None, axis=-1, norm=None): a = asarray(a) if n is None: n = a.shape[axis] - unitary = _unitary(norm) - output = conjugate(rfft(a, n, axis)) - return output * (1 / (sqrt(n) if unitary else n)) + new_norm = _swap_direction(norm) + output = conjugate(rfft(a, n, axis, norm=new_norm)) + return output def _cook_nd_args(a, s=None, axes=None, invreal=0): @@ -683,10 +736,16 @@ def fftn(a, s=None, axes=None, norm=None): axes are used, or all axes if `s` is also not specified. Repeated indices in `axes` means that the transform over that axis is performed multiple times. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -751,7 +810,6 @@ def fftn(a, s=None, axes=None, norm=None): >>> plt.show() """ - return _raw_fftnd(a, s, axes, fft, norm) @@ -790,10 +848,16 @@ def ifftn(a, s=None, axes=None, norm=None): axes are used, or all axes if `s` is also not specified. Repeated indices in `axes` means that the inverse transform over that axis is performed multiple times. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -849,14 +913,13 @@ def ifftn(a, s=None, axes=None, norm=None): >>> plt.show() """ - return _raw_fftnd(a, s, axes, ifft, norm) @array_function_dispatch(_fftn_dispatcher) def fft2(a, s=None, axes=(-2, -1), norm=None): """ - Compute the 2-dimensional discrete Fourier Transform + Compute the 2-dimensional discrete Fourier Transform. This function computes the *n*-dimensional discrete Fourier Transform over any axes in an *M*-dimensional array by means of the @@ -880,10 +943,16 @@ def fft2(a, s=None, axes=(-2, -1), norm=None): axes are used. A repeated index in `axes` means the transform over that axis is performed multiple times. A one-element sequence means that a one-dimensional FFT is performed. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -940,7 +1009,6 @@ def fft2(a, s=None, axes=(-2, -1), norm=None): 0. +0.j , 0. +0.j ]]) """ - return _raw_fftnd(a, s, axes, fft, norm) @@ -978,10 +1046,16 @@ def ifft2(a, s=None, axes=(-2, -1), norm=None): axes are used. A repeated index in `axes` means the transform over that axis is performed multiple times. A one-element sequence means that a one-dimensional FFT is performed. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -1028,7 +1102,6 @@ def ifft2(a, s=None, axes=(-2, -1), norm=None): [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j]]) """ - return _raw_fftnd(a, s, axes, ifft, norm) @@ -1059,10 +1132,16 @@ def rfftn(a, s=None, axes=None, norm=None): axes : sequence of ints, optional Axes over which to compute the FFT. If not given, the last ``len(s)`` axes are used, or all axes if `s` is also not specified. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -1137,10 +1216,16 @@ def rfft2(a, s=None, axes=(-2, -1), norm=None): Shape of the FFT. axes : sequence of ints, optional Axes over which to compute the FFT. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -1158,7 +1243,6 @@ def rfft2(a, s=None, axes=(-2, -1), norm=None): For more details see `rfftn`. """ - return rfftn(a, s, axes, norm) @@ -1190,17 +1274,23 @@ def irfftn(a, s=None, axes=None, norm=None): Along any axis, if the shape indicated by `s` is smaller than that of the input, the input is cropped. If it is larger, the input is padded with zeros. If `s` is not given, the shape of the input along the axes - specified by axes is used. Except for the last axis which is taken to be - ``2*(m-1)`` where ``m`` is the length of the input along that axis. + specified by axes is used. Except for the last axis which is taken to + be ``2*(m-1)`` where ``m`` is the length of the input along that axis. axes : sequence of ints, optional Axes over which to compute the inverse FFT. If not given, the last `len(s)` axes are used, or all axes if `s` is also not specified. Repeated indices in `axes` means that the inverse transform over that axis is performed multiple times. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -1280,10 +1370,16 @@ def irfft2(a, s=None, axes=(-2, -1), norm=None): axes : sequence of ints, optional The axes over which to compute the inverse fft. Default is the last two axes. - norm : {None, "ortho"}, optional + norm : {"backward", "ortho", "forward"}, optional .. versionadded:: 1.10.0 - Normalization mode (see `numpy.fft`). Default is None. + Normalization mode (see `numpy.fft`). Default is "backward". + Indicates which direction of the forward/backward pair of transforms + is scaled and with what normalization factor. + + .. versionadded:: 1.20.0 + + The "backward", "forward" values were added. Returns ------- @@ -1300,5 +1396,4 @@ def irfft2(a, s=None, axes=(-2, -1), norm=None): For more details see `irfftn`. """ - return irfftn(a, s, axes, norm) diff --git a/numpy/fft/tests/test_pocketfft.py b/numpy/fft/tests/test_pocketfft.py index 7c3db0485..604ac8fde 100644 --- a/numpy/fft/tests/test_pocketfft.py +++ b/numpy/fft/tests/test_pocketfft.py @@ -27,19 +27,22 @@ class TestFFT1D: maxlen = 512 x = random(maxlen) + 1j*random(maxlen) xr = random(maxlen) - for i in range(1,maxlen): + for i in range(1, maxlen): assert_allclose(np.fft.ifft(np.fft.fft(x[0:i])), x[0:i], atol=1e-12) - assert_allclose(np.fft.irfft(np.fft.rfft(xr[0:i]),i), + assert_allclose(np.fft.irfft(np.fft.rfft(xr[0:i]), i), xr[0:i], atol=1e-12) def test_fft(self): x = random(30) + 1j*random(30) assert_allclose(fft1(x), np.fft.fft(x), atol=1e-6) + assert_allclose(fft1(x), np.fft.fft(x, norm="backward"), atol=1e-6) assert_allclose(fft1(x) / np.sqrt(30), np.fft.fft(x, norm="ortho"), atol=1e-6) + assert_allclose(fft1(x) / 30., + np.fft.fft(x, norm="forward"), atol=1e-6) - @pytest.mark.parametrize('norm', (None, 'ortho')) + @pytest.mark.parametrize('norm', (None, 'backward', 'ortho', 'forward')) def test_ifft(self, norm): x = random(30) + 1j*random(30) assert_allclose( @@ -54,89 +57,138 @@ class TestFFT1D: x = random((30, 20)) + 1j*random((30, 20)) assert_allclose(np.fft.fft(np.fft.fft(x, axis=1), axis=0), np.fft.fft2(x), atol=1e-6) + assert_allclose(np.fft.fft2(x), + np.fft.fft2(x, norm="backward"), atol=1e-6) assert_allclose(np.fft.fft2(x) / np.sqrt(30 * 20), np.fft.fft2(x, norm="ortho"), atol=1e-6) + assert_allclose(np.fft.fft2(x) / (30. * 20.), + np.fft.fft2(x, norm="forward"), atol=1e-6) def test_ifft2(self): x = random((30, 20)) + 1j*random((30, 20)) assert_allclose(np.fft.ifft(np.fft.ifft(x, axis=1), axis=0), np.fft.ifft2(x), atol=1e-6) + assert_allclose(np.fft.ifft2(x), + np.fft.ifft2(x, norm="backward"), atol=1e-6) assert_allclose(np.fft.ifft2(x) * np.sqrt(30 * 20), np.fft.ifft2(x, norm="ortho"), atol=1e-6) + assert_allclose(np.fft.ifft2(x) * (30. * 20.), + np.fft.ifft2(x, norm="forward"), atol=1e-6) def test_fftn(self): x = random((30, 20, 10)) + 1j*random((30, 20, 10)) assert_allclose( np.fft.fft(np.fft.fft(np.fft.fft(x, axis=2), axis=1), axis=0), np.fft.fftn(x), atol=1e-6) + assert_allclose(np.fft.fftn(x), + np.fft.fftn(x, norm="backward"), atol=1e-6) assert_allclose(np.fft.fftn(x) / np.sqrt(30 * 20 * 10), np.fft.fftn(x, norm="ortho"), atol=1e-6) + assert_allclose(np.fft.fftn(x) / (30. * 20. * 10.), + np.fft.fftn(x, norm="forward"), atol=1e-6) def test_ifftn(self): x = random((30, 20, 10)) + 1j*random((30, 20, 10)) assert_allclose( np.fft.ifft(np.fft.ifft(np.fft.ifft(x, axis=2), axis=1), axis=0), np.fft.ifftn(x), atol=1e-6) + assert_allclose(np.fft.ifftn(x), + np.fft.ifftn(x, norm="backward"), atol=1e-6) assert_allclose(np.fft.ifftn(x) * np.sqrt(30 * 20 * 10), np.fft.ifftn(x, norm="ortho"), atol=1e-6) + assert_allclose(np.fft.ifftn(x) * (30. * 20. * 10.), + np.fft.ifftn(x, norm="forward"), atol=1e-6) def test_rfft(self): x = random(30) for n in [x.size, 2*x.size]: - for norm in [None, 'ortho']: + for norm in [None, 'backward', 'ortho', 'forward']: assert_allclose( np.fft.fft(x, n=n, norm=norm)[:(n//2 + 1)], np.fft.rfft(x, n=n, norm=norm), atol=1e-6) assert_allclose( + np.fft.rfft(x, n=n), + np.fft.rfft(x, n=n, norm="backward"), atol=1e-6) + assert_allclose( np.fft.rfft(x, n=n) / np.sqrt(n), np.fft.rfft(x, n=n, norm="ortho"), atol=1e-6) + assert_allclose( + np.fft.rfft(x, n=n) / n, + np.fft.rfft(x, n=n, norm="forward"), atol=1e-6) def test_irfft(self): x = random(30) assert_allclose(x, np.fft.irfft(np.fft.rfft(x)), atol=1e-6) - assert_allclose( - x, np.fft.irfft(np.fft.rfft(x, norm="ortho"), norm="ortho"), atol=1e-6) + assert_allclose(x, np.fft.irfft(np.fft.rfft(x, norm="backward"), + norm="backward"), atol=1e-6) + assert_allclose(x, np.fft.irfft(np.fft.rfft(x, norm="ortho"), + norm="ortho"), atol=1e-6) + assert_allclose(x, np.fft.irfft(np.fft.rfft(x, norm="forward"), + norm="forward"), atol=1e-6) def test_rfft2(self): x = random((30, 20)) assert_allclose(np.fft.fft2(x)[:, :11], np.fft.rfft2(x), atol=1e-6) + assert_allclose(np.fft.rfft2(x), + np.fft.rfft2(x, norm="backward"), atol=1e-6) assert_allclose(np.fft.rfft2(x) / np.sqrt(30 * 20), np.fft.rfft2(x, norm="ortho"), atol=1e-6) + assert_allclose(np.fft.rfft2(x) / (30. * 20.), + np.fft.rfft2(x, norm="forward"), atol=1e-6) def test_irfft2(self): x = random((30, 20)) assert_allclose(x, np.fft.irfft2(np.fft.rfft2(x)), atol=1e-6) - assert_allclose( - x, np.fft.irfft2(np.fft.rfft2(x, norm="ortho"), norm="ortho"), atol=1e-6) + assert_allclose(x, np.fft.irfft2(np.fft.rfft2(x, norm="backward"), + norm="backward"), atol=1e-6) + assert_allclose(x, np.fft.irfft2(np.fft.rfft2(x, norm="ortho"), + norm="ortho"), atol=1e-6) + assert_allclose(x, np.fft.irfft2(np.fft.rfft2(x, norm="forward"), + norm="forward"), atol=1e-6) def test_rfftn(self): x = random((30, 20, 10)) assert_allclose(np.fft.fftn(x)[:, :, :6], np.fft.rfftn(x), atol=1e-6) + assert_allclose(np.fft.rfftn(x), + np.fft.rfftn(x, norm="backward"), atol=1e-6) assert_allclose(np.fft.rfftn(x) / np.sqrt(30 * 20 * 10), np.fft.rfftn(x, norm="ortho"), atol=1e-6) + assert_allclose(np.fft.rfftn(x) / (30. * 20. * 10.), + np.fft.rfftn(x, norm="forward"), atol=1e-6) def test_irfftn(self): x = random((30, 20, 10)) assert_allclose(x, np.fft.irfftn(np.fft.rfftn(x)), atol=1e-6) - assert_allclose( - x, np.fft.irfftn(np.fft.rfftn(x, norm="ortho"), norm="ortho"), atol=1e-6) + assert_allclose(x, np.fft.irfftn(np.fft.rfftn(x, norm="backward"), + norm="backward"), atol=1e-6) + assert_allclose(x, np.fft.irfftn(np.fft.rfftn(x, norm="ortho"), + norm="ortho"), atol=1e-6) + assert_allclose(x, np.fft.irfftn(np.fft.rfftn(x, norm="forward"), + norm="forward"), atol=1e-6) def test_hfft(self): x = random(14) + 1j*random(14) x_herm = np.concatenate((random(1), x, random(1))) x = np.concatenate((x_herm, x[::-1].conj())) assert_allclose(np.fft.fft(x), np.fft.hfft(x_herm), atol=1e-6) + assert_allclose(np.fft.hfft(x_herm), + np.fft.hfft(x_herm, norm="backward"), atol=1e-6) assert_allclose(np.fft.hfft(x_herm) / np.sqrt(30), np.fft.hfft(x_herm, norm="ortho"), atol=1e-6) + assert_allclose(np.fft.hfft(x_herm) / 30., + np.fft.hfft(x_herm, norm="forward"), atol=1e-6) - def test_ihttf(self): + def test_ihfft(self): x = random(14) + 1j*random(14) x_herm = np.concatenate((random(1), x, random(1))) x = np.concatenate((x_herm, x[::-1].conj())) assert_allclose(x_herm, np.fft.ihfft(np.fft.hfft(x_herm)), atol=1e-6) - assert_allclose( - x_herm, np.fft.ihfft(np.fft.hfft(x_herm, norm="ortho"), - norm="ortho"), atol=1e-6) + assert_allclose(x_herm, np.fft.ihfft(np.fft.hfft(x_herm, + norm="backward"), norm="backward"), atol=1e-6) + assert_allclose(x_herm, np.fft.ihfft(np.fft.hfft(x_herm, + norm="ortho"), norm="ortho"), atol=1e-6) + assert_allclose(x_herm, np.fft.ihfft(np.fft.hfft(x_herm, + norm="forward"), norm="forward"), atol=1e-6) @pytest.mark.parametrize("op", [np.fft.fftn, np.fft.ifftn, np.fft.rfftn, np.fft.irfftn]) @@ -161,7 +213,7 @@ class TestFFT1D: ] for forw, back in func_pairs: for n in [x.size, 2*x.size]: - for norm in [None, 'ortho']: + for norm in [None, 'backward', 'ortho', 'forward']: tmp = forw(x, n=n, norm=norm) tmp = back(tmp, n=n, norm=norm) assert_allclose(x_norm, diff --git a/numpy/lib/_datasource.py b/numpy/lib/_datasource.py index f5d0cc217..7a23b1651 100644 --- a/numpy/lib/_datasource.py +++ b/numpy/lib/_datasource.py @@ -37,7 +37,6 @@ Example:: import os import shutil import io -from contextlib import closing from numpy.core.overrides import set_module @@ -333,12 +332,9 @@ class DataSource: # TODO: Doesn't handle compressed files! if self._isurl(path): - try: - with closing(urlopen(path)) as openedurl: - with _open(upath, 'wb') as f: - shutil.copyfileobj(openedurl, f) - except URLError: - raise URLError("URL not found: %s" % path) + with urlopen(path) as openedurl: + with _open(upath, 'wb') as f: + shutil.copyfileobj(openedurl, f) else: shutil.copyfile(path, upath) return upath diff --git a/numpy/lib/arraysetops.py b/numpy/lib/arraysetops.py index 22687b941..6cca1738b 100644 --- a/numpy/lib/arraysetops.py +++ b/numpy/lib/arraysetops.py @@ -206,6 +206,7 @@ def unique(ar, return_index=False, return_inverse=False, -------- numpy.lib.arraysetops : Module with a number of other functions for performing set operations on arrays. + repeat : Repeat elements of an array. Notes ----- @@ -244,7 +245,7 @@ def unique(ar, return_index=False, return_inverse=False, >>> a[indices] array(['a', 'b', 'c'], dtype='<U1') - Reconstruct the input array from the unique values: + Reconstruct the input array from the unique values and inverse: >>> a = np.array([1, 2, 6, 4, 2, 3, 2]) >>> u, indices = np.unique(a, return_inverse=True) @@ -255,6 +256,17 @@ def unique(ar, return_index=False, return_inverse=False, >>> u[indices] array([1, 2, 6, 4, 2, 3, 2]) + Reconstruct the input values from the unique values and counts: + + >>> a = np.array([1, 2, 6, 4, 2, 3, 2]) + >>> values, counts = np.unique(a, return_counts=True) + >>> values + array([1, 2, 3, 4, 6]) + >>> counts + array([1, 3, 1, 1, 1]) + >>> np.repeat(values, counts) + array([1, 2, 2, 2, 3, 4, 6]) # original order not preserved + """ ar = np.asanyarray(ar) if axis is None: diff --git a/numpy/lib/format.py b/numpy/lib/format.py index cb2d511af..4e6e731c1 100644 --- a/numpy/lib/format.py +++ b/numpy/lib/format.py @@ -280,14 +280,26 @@ def dtype_to_descr(dtype): return dtype.str def descr_to_dtype(descr): - ''' - descr may be stored as dtype.descr, which is a list of - (name, format, [shape]) tuples where format may be a str or a tuple. - Offsets are not explicitly saved, rather empty fields with - name, format == '', '|Vn' are added as padding. - - This function reverses the process, eliminating the empty padding fields. - ''' + """ + Returns a dtype based off the given description. + + This is essentially the reverse of `dtype_to_descr()`. It will remove + the valueless padding fields created by, i.e. simple fields like + dtype('float32'), and then convert the description to its corresponding + dtype. + + Parameters + ---------- + descr : object + The object retreived by dtype.descr. Can be passed to + `numpy.dtype()` in order to replicate the input dtype. + + Returns + ------- + dtype : dtype + The dtype constructed by the description. + + """ if isinstance(descr, str): # No padding removal needed return numpy.dtype(descr) @@ -820,7 +832,7 @@ def open_memmap(filename, mode='r+', dtype=None, shape=None, See Also -------- - memmap + numpy.memmap """ if isfileobj(filename): diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index cf6f4891c..6ea9cc4de 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -431,10 +431,13 @@ def asarray_chkfinite(a, dtype=None, order=None): of lists and ndarrays. Success requires no NaNs or Infs. dtype : data-type, optional By default, the data-type is inferred from the input data. - order : {'C', 'F'}, optional - Whether to use row-major (C-style) or - column-major (Fortran-style) memory representation. - Defaults to 'C'. + order : {'C', 'F', 'A', 'K'}, optional + Memory layout. 'A' and 'K' depend on the order of input array a. + 'C' row-major (C-style), + 'F' column-major (Fortran-style) memory representation. + 'A' (any) means 'F' if `a` is Fortran contiguous, 'C' otherwise + 'K' (keep) preserve input order + Defaults to 'C'. Returns ------- @@ -2543,6 +2546,69 @@ def corrcoef(x, y=None, rowvar=True, bias=np._NoValue, ddof=np._NoValue): for backwards compatibility with previous versions of this function. These arguments had no effect on the return values of the function and can be safely ignored in this and previous versions of numpy. + + Examples + -------- + In this example we generate two random arrays, ``xarr`` and ``yarr``, and + compute the row-wise and column-wise Pearson correlation coefficients, + ``R``. Since ``rowvar`` is true by default, we first find the row-wise + Pearson correlation coefficients between the variables of ``xarr``. + + >>> import numpy as np + >>> rng = np.random.default_rng(seed=42) + >>> xarr = rng.random((3, 3)) + >>> xarr + array([[0.77395605, 0.43887844, 0.85859792], + [0.69736803, 0.09417735, 0.97562235], + [0.7611397 , 0.78606431, 0.12811363]]) + >>> R1 = np.corrcoef(xarr) + >>> R1 + array([[ 1. , 0.99256089, -0.68080986], + [ 0.99256089, 1. , -0.76492172], + [-0.68080986, -0.76492172, 1. ]]) + + If we add another set of variables and observations ``yarr``, we can + compute the row-wise Pearson correlation coefficients between the + variables in ``xarr`` and ``yarr``. + + >>> yarr = rng.random((3, 3)) + >>> yarr + array([[0.45038594, 0.37079802, 0.92676499], + [0.64386512, 0.82276161, 0.4434142 ], + [0.22723872, 0.55458479, 0.06381726]]) + >>> R2 = np.corrcoef(xarr, yarr) + >>> R2 + array([[ 1. , 0.99256089, -0.68080986, 0.75008178, -0.934284 , + -0.99004057], + [ 0.99256089, 1. , -0.76492172, 0.82502011, -0.97074098, + -0.99981569], + [-0.68080986, -0.76492172, 1. , -0.99507202, 0.89721355, + 0.77714685], + [ 0.75008178, 0.82502011, -0.99507202, 1. , -0.93657855, + -0.83571711], + [-0.934284 , -0.97074098, 0.89721355, -0.93657855, 1. , + 0.97517215], + [-0.99004057, -0.99981569, 0.77714685, -0.83571711, 0.97517215, + 1. ]]) + + Finally if we use the option ``rowvar=False``, the columns are now + being treated as the variables and we will find the column-wise Pearson + correlation coefficients between variables in ``xarr`` and ``yarr``. + + >>> R3 = np.corrcoef(xarr, yarr, rowvar=False) + >>> R3 + array([[ 1. , 0.77598074, -0.47458546, -0.75078643, -0.9665554 , + 0.22423734], + [ 0.77598074, 1. , -0.92346708, -0.99923895, -0.58826587, + -0.44069024], + [-0.47458546, -0.92346708, 1. , 0.93773029, 0.23297648, + 0.75137473], + [-0.75078643, -0.99923895, 0.93773029, 1. , 0.55627469, + 0.47536961], + [-0.9665554 , -0.58826587, 0.23297648, 0.55627469, 1. , + -0.46666491], + [ 0.22423734, -0.44069024, 0.75137473, 0.47536961, -0.46666491, + 1. ]]) """ if bias is not np._NoValue or ddof is not np._NoValue: @@ -3879,7 +3945,13 @@ def _quantile_is_valid(q): def _lerp(a, b, t, out=None): """ Linearly interpolate from a to b by a factor of t """ - return add(a*(1 - t), b*t, out=out) + diff_b_a = subtract(b, a) + # asanyarray is a stop-gap until gh-13105 + lerp_interpolation = asanyarray(add(a, diff_b_a*t, out=out)) + subtract(b, diff_b_a * (1 - t), out=lerp_interpolation, where=t>=0.5) + if lerp_interpolation.ndim == 0 and out is None: + lerp_interpolation = lerp_interpolation[()] # unpack 0d arrays + return lerp_interpolation def _quantile_ureduce_func(a, q, axis=None, out=None, overwrite_input=False, diff --git a/numpy/lib/index_tricks.py b/numpy/lib/index_tricks.py index d145477c3..cba713ede 100644 --- a/numpy/lib/index_tricks.py +++ b/numpy/lib/index_tricks.py @@ -221,7 +221,7 @@ class MGridClass(nd_grid): the stop value **is inclusive**. Returns - ---------- + ------- mesh-grid `ndarrays` all of the same dimensions See Also @@ -611,8 +611,9 @@ class ndindex: Parameters ---------- - `*args` : ints - The size of each dimension of the array. + shape : ints, or a single tuple of ints + The size of each dimension of the array can be passed as + individual parameters or as the elements of a tuple. See Also -------- @@ -620,6 +621,7 @@ class ndindex: Examples -------- + # dimensions as individual arguments >>> for index in np.ndindex(3, 2, 1): ... print(index) (0, 0, 0) @@ -629,6 +631,16 @@ class ndindex: (2, 0, 0) (2, 1, 0) + # same dimensions - but in a tuple (3, 2, 1) + >>> for index in np.ndindex((3, 2, 1)): + ... print(index) + (0, 0, 0) + (0, 1, 0) + (1, 0, 0) + (1, 1, 0) + (2, 0, 0) + (2, 1, 0) + """ def __init__(self, *shape): @@ -896,7 +908,7 @@ def diag_indices(n, ndim=2): ndim : int, optional The number of dimensions. - See also + See Also -------- diag_indices_from diff --git a/numpy/lib/npyio.py b/numpy/lib/npyio.py index f5a548433..520e9c9ec 100644 --- a/numpy/lib/npyio.py +++ b/numpy/lib/npyio.py @@ -784,6 +784,7 @@ def _getconv(dtype): else: return asstr + # amount of lines loadtxt reads in one chunk, can be overridden for testing _loadtxt_chunksize = 50000 @@ -914,68 +915,10 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None, [ 19.22, 64.31], [-17.57, 63.94]]) """ - # Type conversions for Py3 convenience - if comments is not None: - if isinstance(comments, (str, bytes)): - comments = [comments] - comments = [_decode_line(x) for x in comments] - # Compile regex for comments beforehand - comments = (re.escape(comment) for comment in comments) - regex_comments = re.compile('|'.join(comments)) - if delimiter is not None: - delimiter = _decode_line(delimiter) - - user_converters = converters - - if encoding == 'bytes': - encoding = None - byte_converters = True - else: - byte_converters = False - - if usecols is not None: - # Allow usecols to be a single int or a sequence of ints - try: - usecols_as_list = list(usecols) - except TypeError: - usecols_as_list = [usecols] - for col_idx in usecols_as_list: - try: - opindex(col_idx) - except TypeError as e: - e.args = ( - "usecols must be an int or a sequence of ints but " - "it contains at least one element of type %s" % - type(col_idx), - ) - raise - # Fall back to existing code - usecols = usecols_as_list - - fown = False - try: - if isinstance(fname, os_PathLike): - fname = os_fspath(fname) - if _is_string_like(fname): - fh = np.lib._datasource.open(fname, 'rt', encoding=encoding) - fencoding = getattr(fh, 'encoding', 'latin1') - fh = iter(fh) - fown = True - else: - fh = iter(fname) - fencoding = getattr(fname, 'encoding', 'latin1') - except TypeError: - raise ValueError('fname must be a string, file handle, or generator') - - # input may be a python2 io stream - if encoding is not None: - fencoding = encoding - # we must assume local encoding - # TODO emit portability warning? - elif fencoding is None: - import locale - fencoding = locale.getpreferredencoding() + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # Nested functions used by loadtxt. + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # not to be confused with the flatten_dtype we import... @recursive @@ -1075,11 +1018,86 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None, if X: yield X + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # Main body of loadtxt. + # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + # Check correctness of the values of `ndmin` + if ndmin not in [0, 1, 2]: + raise ValueError('Illegal value of ndmin keyword: %s' % ndmin) + + # Type conversions for Py3 convenience + if comments is not None: + if isinstance(comments, (str, bytes)): + comments = [comments] + comments = [_decode_line(x) for x in comments] + # Compile regex for comments beforehand + comments = (re.escape(comment) for comment in comments) + regex_comments = re.compile('|'.join(comments)) + + if delimiter is not None: + delimiter = _decode_line(delimiter) + + user_converters = converters + + if encoding == 'bytes': + encoding = None + byte_converters = True + else: + byte_converters = False + + if usecols is not None: + # Allow usecols to be a single int or a sequence of ints + try: + usecols_as_list = list(usecols) + except TypeError: + usecols_as_list = [usecols] + for col_idx in usecols_as_list: + try: + opindex(col_idx) + except TypeError as e: + e.args = ( + "usecols must be an int or a sequence of ints but " + "it contains at least one element of type %s" % + type(col_idx), + ) + raise + # Fall back to existing code + usecols = usecols_as_list + + # Make sure we're dealing with a proper dtype + dtype = np.dtype(dtype) + defconv = _getconv(dtype) + + dtype_types, packing = flatten_dtype_internal(dtype) + + fown = False try: - # Make sure we're dealing with a proper dtype - dtype = np.dtype(dtype) - defconv = _getconv(dtype) + if isinstance(fname, os_PathLike): + fname = os_fspath(fname) + if _is_string_like(fname): + fh = np.lib._datasource.open(fname, 'rt', encoding=encoding) + fencoding = getattr(fh, 'encoding', 'latin1') + fh = iter(fh) + fown = True + else: + fh = iter(fname) + fencoding = getattr(fname, 'encoding', 'latin1') + except TypeError as e: + raise ValueError( + 'fname must be a string, file handle, or generator' + ) from e + # input may be a python2 io stream + if encoding is not None: + fencoding = encoding + # we must assume local encoding + # TODO emit portability warning? + elif fencoding is None: + import locale + fencoding = locale.getpreferredencoding() + + try: # Skip the first `skiprows` lines for i in range(skiprows): next(fh) @@ -1095,10 +1113,12 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None, # End of lines reached first_line = '' first_vals = [] - warnings.warn('loadtxt: Empty input file: "%s"' % fname, stacklevel=2) + warnings.warn('loadtxt: Empty input file: "%s"' % fname, + stacklevel=2) N = len(usecols or first_vals) - dtype_types, packing = flatten_dtype_internal(dtype) + # Now that we know N, create the default converters list, and + # set packing, if necessary. if len(dtype_types) > 1: # We're dealing with a structured array, each field of # the dtype matches a column @@ -1118,8 +1138,9 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None, # Unused converter specified continue if byte_converters: - # converters may use decode to workaround numpy's old behaviour, - # so encode the string again before passing to the user converter + # converters may use decode to workaround numpy's old + # behaviour, so encode the string again before passing to + # the user converter def tobytes_first(x, conv): if type(x) is bytes: return conv(x) @@ -1158,9 +1179,6 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None, X.shape = (1, -1) # Verify that the array has at least dimensions `ndmin`. - # Check correctness of the values of `ndmin` - if ndmin not in [0, 1, 2]: - raise ValueError('Illegal value of ndmin keyword: %s' % ndmin) # Tweak the size and shape of the arrays - remove extraneous dimensions if X.ndim > ndmin: X = np.squeeze(X) @@ -1667,7 +1685,7 @@ def genfromtxt(fname, dtype=float, comments='#', delimiter=None, <https://docs.scipy.org/doc/numpy/user/basics.io.genfromtxt.html>`_. Examples - --------- + -------- >>> from io import StringIO >>> import numpy as np @@ -1752,10 +1770,10 @@ def genfromtxt(fname, dtype=float, comments='#', delimiter=None, fid = fname fid_ctx = contextlib_nullcontext(fid) fhd = iter(fid) - except TypeError: + except TypeError as e: raise TypeError( "fname must be a string, filehandle, list of strings, " - "or generator. Got %s instead." % type(fname)) + "or generator. Got %s instead." % type(fname)) from e with fid_ctx: split_line = LineSplitter(delimiter=delimiter, comments=comments, diff --git a/numpy/lib/polynomial.py b/numpy/lib/polynomial.py index 5a0fa5431..1c124cc0e 100644 --- a/numpy/lib/polynomial.py +++ b/numpy/lib/polynomial.py @@ -46,6 +46,12 @@ def poly(seq_of_zeros): """ Find the coefficients of a polynomial with the given sequence of roots. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + Returns the coefficients of the polynomial whose leading coefficient is one for the given sequence of zeros (multiple roots must be included in the sequence as many times as their multiplicity; see Examples). @@ -168,6 +174,12 @@ def roots(p): """ Return the roots of a polynomial with coefficients given in p. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + The values in the rank-1 array `p` are coefficients of a polynomial. If the length of `p` is n+1 then the polynomial is described by:: @@ -258,6 +270,12 @@ def polyint(p, m=1, k=None): """ Return an antiderivative (indefinite integral) of a polynomial. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + The returned order `m` antiderivative `P` of polynomial `p` satisfies :math:`\\frac{d^m}{dx^m}P(x) = p(x)` and is defined up to `m - 1` integration constants `k`. The constants determine the low-order @@ -357,6 +375,12 @@ def polyder(p, m=1): """ Return the derivative of the specified order of a polynomial. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + Parameters ---------- p : poly1d or sequence @@ -431,6 +455,12 @@ def polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False): """ Least squares polynomial fit. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + Fit a polynomial ``p(x) = p[0] * x**deg + ... + p[deg]`` of degree `deg` to points `(x, y)`. Returns a vector of coefficients `p` that minimises the squared error in the order `deg`, `deg-1`, ... `0`. @@ -464,11 +494,12 @@ def polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False): cov : bool or str, optional If given and not `False`, return not just the estimate but also its covariance matrix. By default, the covariance are scaled by - chi2/sqrt(N-dof), i.e., the weights are presumed to be unreliable - except in a relative sense and everything is scaled such that the - reduced chi2 is unity. This scaling is omitted if ``cov='unscaled'``, - as is relevant for the case that the weights are 1/sigma**2, with - sigma known to be a reliable estimate of the uncertainty. + chi2/dof, where dof = M - (deg + 1), i.e., the weights are presumed + to be unreliable except in a relative sense and everything is scaled + such that the reduced chi2 is unity. This scaling is omitted if + ``cov='unscaled'``, as is relevant for the case that the weights are + 1/sigma**2, with sigma known to be a reliable estimate of the + uncertainty. Returns ------- @@ -667,6 +698,12 @@ def polyval(p, x): """ Evaluate a polynomial at specific values. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + If `p` is of length N, this function returns the value: ``p[0]*x**(N-1) + p[1]*x**(N-2) + ... + p[N-2]*x + p[N-1]`` @@ -744,6 +781,12 @@ def polyadd(a1, a2): """ Find the sum of two polynomials. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + Returns the polynomial resulting from the sum of two input polynomials. Each input must be either a poly1d object or a 1D sequence of polynomial coefficients, from highest to lowest degree. @@ -806,6 +849,12 @@ def polysub(a1, a2): """ Difference (subtraction) of two polynomials. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + Given two polynomials `a1` and `a2`, returns ``a1 - a2``. `a1` and `a2` can be either array_like sequences of the polynomials' coefficients (including coefficients equal to zero), or `poly1d` objects. @@ -854,6 +903,12 @@ def polymul(a1, a2): """ Find the product of two polynomials. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + Finds the polynomial resulting from the multiplication of the two input polynomials. Each input must be either a poly1d object or a 1D sequence of polynomial coefficients, from highest to lowest degree. @@ -915,6 +970,12 @@ def polydiv(u, v): """ Returns the quotient and remainder of polynomial division. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + The input arrays are the coefficients (including any coefficients equal to zero) of the "numerator" (dividend) and "denominator" (divisor) polynomials, respectively. @@ -1009,6 +1070,12 @@ class poly1d: """ A one-dimensional polynomial class. + .. note:: + This forms part of the old polynomial API. Since version 1.4, the + new polynomial API defined in `numpy.polynomial` is preferred. + A summary of the differences can be found in the + :doc:`transition guide </reference/routines.polynomials>`. + A convenience class, used to encapsulate "natural" operations on polynomials so that said operations may take on their customary form in code (see Examples). diff --git a/numpy/lib/shape_base.py b/numpy/lib/shape_base.py index 78703555e..bc6718eca 100644 --- a/numpy/lib/shape_base.py +++ b/numpy/lib/shape_base.py @@ -452,7 +452,7 @@ def apply_over_axes(func, a, axes): Apply a function to 1-D slices of an array along the given axis. Notes - ------ + ----- This function is equivalent to tuple axis arguments to reorderable ufuncs with keepdims=True. Tuple axis arguments to ufuncs have been available since version 1.7.0. diff --git a/numpy/lib/tests/test_function_base.py b/numpy/lib/tests/test_function_base.py index 9ba0be56a..eb2fc3311 100644 --- a/numpy/lib/tests/test_function_base.py +++ b/numpy/lib/tests/test_function_base.py @@ -5,6 +5,10 @@ import decimal from fractions import Fraction import math import pytest +import hypothesis +from hypothesis.extra.numpy import arrays +import hypothesis.strategies as st + import numpy as np from numpy import ma @@ -3104,6 +3108,74 @@ class TestQuantile: np.quantile(np.arange(100.), p, interpolation="midpoint") assert_array_equal(p, p0) + def test_quantile_monotonic(self): + # GH 14685 + # test that the return value of quantile is monotonic if p0 is ordered + p0 = np.arange(0, 1, 0.01) + quantile = np.quantile(np.array([0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 1, 1, 9, 9, 9, + 8, 8, 7]) * 0.1, p0) + assert_equal(np.sort(quantile), quantile) + + @hypothesis.given( + arr=arrays(dtype=np.float64, + shape=st.integers(min_value=3, max_value=1000), + elements=st.floats(allow_infinity=False, allow_nan=False, + min_value=-1e300, max_value=1e300))) + def test_quantile_monotonic_hypo(self, arr): + p0 = np.arange(0, 1, 0.01) + quantile = np.quantile(arr, p0) + assert_equal(np.sort(quantile), quantile) + + +class TestLerp: + @hypothesis.given(t0=st.floats(allow_nan=False, allow_infinity=False, + min_value=0, max_value=1), + t1=st.floats(allow_nan=False, allow_infinity=False, + min_value=0, max_value=1), + a = st.floats(allow_nan=False, allow_infinity=False, + min_value=-1e300, max_value=1e300), + b = st.floats(allow_nan=False, allow_infinity=False, + min_value=-1e300, max_value=1e300)) + def test_lerp_monotonic(self, t0, t1, a, b): + l0 = np.lib.function_base._lerp(a, b, t0) + l1 = np.lib.function_base._lerp(a, b, t1) + if t0 == t1 or a == b: + assert l0 == l1 # uninteresting + elif (t0 < t1) == (a < b): + assert l0 <= l1 + else: + assert l0 >= l1 + + @hypothesis.given(t=st.floats(allow_nan=False, allow_infinity=False, + min_value=0, max_value=1), + a=st.floats(allow_nan=False, allow_infinity=False, + min_value=-1e300, max_value=1e300), + b=st.floats(allow_nan=False, allow_infinity=False, + min_value=-1e300, max_value=1e300)) + def test_lerp_bounded(self, t, a, b): + if a <= b: + assert a <= np.lib.function_base._lerp(a, b, t) <= b + else: + assert b <= np.lib.function_base._lerp(a, b, t) <= a + + @hypothesis.given(t=st.floats(allow_nan=False, allow_infinity=False, + min_value=0, max_value=1), + a=st.floats(allow_nan=False, allow_infinity=False, + min_value=-1e300, max_value=1e300), + b=st.floats(allow_nan=False, allow_infinity=False, + min_value=-1e300, max_value=1e300)) + def test_lerp_symmetric(self, t, a, b): + # double subtraction is needed to remove the extra precision of t < 0.5 + left = np.lib.function_base._lerp(a, b, 1 - (1 - t)) + right = np.lib.function_base._lerp(b, a, 1 - t) + assert left == right + + def test_lerp_0d_inputs(self): + a = np.array(2) + b = np.array(5) + t = np.array(0.2) + assert np.lib.function_base._lerp(a, b, t) == 2.6 + class TestMedian: diff --git a/numpy/lib/tests/test_nanfunctions.py b/numpy/lib/tests/test_nanfunctions.py index db563e30c..e0f723a3c 100644 --- a/numpy/lib/tests/test_nanfunctions.py +++ b/numpy/lib/tests/test_nanfunctions.py @@ -957,7 +957,7 @@ def test__replace_nan(): """ Test that _replace_nan returns the original array if there are no NaNs, not a copy. """ - for dtype in [np.bool, np.int32, np.int64]: + for dtype in [np.bool_, np.int32, np.int64]: arr = np.array([0, 1], dtype=dtype) result, mask = _replace_nan(arr, 0) assert mask is None diff --git a/numpy/lib/twodim_base.py b/numpy/lib/twodim_base.py index 320a24856..cd7484241 100644 --- a/numpy/lib/twodim_base.py +++ b/numpy/lib/twodim_base.py @@ -441,7 +441,7 @@ def triu(m, k=0): """ Upper triangle of an array. - Return a copy of a matrix with the elements below the `k`-th diagonal + Return a copy of an array with the elements below the `k`-th diagonal zeroed. Please refer to the documentation for `tril` for further details. @@ -675,7 +675,7 @@ def histogram2d(x, y, bins=10, range=None, normed=None, weights=None, >>> fig = plt.figure(figsize=(7, 3)) >>> ax = fig.add_subplot(131, title='imshow: square bins') - >>> plt.imshow(H, interpolation='nearest', origin='low', + >>> plt.imshow(H, interpolation='nearest', origin='lower', ... extent=[xedges[0], xedges[-1], yedges[0], yedges[-1]]) <matplotlib.image.AxesImage object at 0x...> diff --git a/numpy/linalg/linalg.py b/numpy/linalg/linalg.py index 5ee326f3c..92f93d671 100644 --- a/numpy/linalg/linalg.py +++ b/numpy/linalg/linalg.py @@ -176,10 +176,9 @@ def _to_native_byte_order(*arrays): def _fastCopyAndTranspose(type, *arrays): cast_arrays = () for a in arrays: - if a.dtype.type is type: - cast_arrays = cast_arrays + (_fastCT(a),) - else: - cast_arrays = cast_arrays + (_fastCT(a.astype(type)),) + if a.dtype.type is not type: + a = a.astype(type) + cast_arrays = cast_arrays + (_fastCT(a),) if len(cast_arrays) == 1: return cast_arrays[0] else: @@ -2660,7 +2659,7 @@ def multi_dot(arrays, *, out=None): See Also -------- - dot : dot multiplication with two arguments. + numpy.dot : dot multiplication with two arguments. References ---------- diff --git a/numpy/ma/core.py b/numpy/ma/core.py index 8d612b8ed..b5371f51a 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -21,6 +21,7 @@ Released for unlimited redistribution. """ # pylint: disable-msg=E1002 import builtins +import inspect import operator import warnings import textwrap @@ -122,15 +123,8 @@ def doc_note(initialdoc, note): if note is None: return initialdoc - notesplit = re.split(r'\n\s*?Notes\n\s*?-----', initialdoc) - - notedoc = """\ -Notes - ----- - %s""" % note - - if len(notesplit) > 1: - notedoc = '\n\n ' + notedoc + '\n' + notesplit = re.split(r'\n\s*?Notes\n\s*?-----', inspect.cleandoc(initialdoc)) + notedoc = "\n\nNotes\n-----\n%s\n" % inspect.cleandoc(note) return ''.join(notesplit[:1] + [notedoc] + notesplit[1:]) diff --git a/numpy/ma/extras.py b/numpy/ma/extras.py index f86ebf551..8ede29da1 100644 --- a/numpy/ma/extras.py +++ b/numpy/ma/extras.py @@ -244,11 +244,6 @@ class _fromnxfunction: the new masked array version of the function. A note on application of the function to the mask is appended. - .. warning:: - If the function docstring already contained a Notes section, the - new docstring will have two Notes sections instead of appending a note - to the existing section. - Parameters ---------- None @@ -258,9 +253,9 @@ class _fromnxfunction: doc = getattr(npfunc, '__doc__', None) if doc: sig = self.__name__ + ma.get_object_signature(npfunc) - locdoc = "Notes\n-----\nThe function is applied to both the _data"\ - " and the _mask, if any." - return '\n'.join((sig, doc, locdoc)) + doc = ma.doc_note(doc, "The function is applied to both the _data " + "and the _mask, if any.") + return '\n\n'.join((sig, doc)) return def __call__(self, *args, **params): diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py index 6f34144bb..27f14a5e7 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -34,8 +34,8 @@ from numpy.ma.core import ( MAError, MaskError, MaskType, MaskedArray, abs, absolute, add, all, allclose, allequal, alltrue, angle, anom, arange, arccos, arccosh, arctan2, arcsin, arctan, argsort, array, asarray, choose, concatenate, - conjugate, cos, cosh, count, default_fill_value, diag, divide, empty, - empty_like, equal, exp, flatten_mask, filled, fix_invalid, + conjugate, cos, cosh, count, default_fill_value, diag, divide, doc_note, + empty, empty_like, equal, exp, flatten_mask, filled, fix_invalid, flatten_structured_array, fromflex, getmask, getmaskarray, greater, greater_equal, identity, inner, isMaskedArray, less, less_equal, log, log10, make_mask, make_mask_descr, mask_or, masked, masked_array, @@ -215,6 +215,17 @@ class TestMaskedArray: y = array([1, 2, 3], mask=x._mask, copy=True) assert_(not np.may_share_memory(x.mask, y.mask)) + def test_masked_singleton_array_creation_warns(self): + # The first works, but should not (ideally), there may be no way + # to solve this, however, as long as `np.ma.masked` is an ndarray. + np.array(np.ma.masked) + with pytest.warns(UserWarning): + # Tries to create a float array, using `float(np.ma.masked)`. + # We may want to define this is invalid behaviour in the future! + # (requiring np.ma.masked to be a known NumPy scalar probably + # with a DType.) + np.array([3., np.ma.masked]) + def test_creation_with_list_of_maskedarrays(self): # Tests creating a masked array from a list of masked arrays. x = array(np.arange(5), mask=[1, 0, 0, 0, 0]) @@ -5283,3 +5294,33 @@ def test_mask_shape_assignment_does_not_break_masked(): b = np.ma.array(1, mask=a.mask) b.shape = (1,) assert_equal(a.mask.shape, ()) + +@pytest.mark.skipif(sys.flags.optimize > 1, + reason="no docstrings present to inspect when PYTHONOPTIMIZE/Py_OptimizeFlag > 1") +def test_doc_note(): + def method(self): + """This docstring + + Has multiple lines + + And notes + + Notes + ----- + original note + """ + pass + + expected_doc = """This docstring + +Has multiple lines + +And notes + +Notes +----- +note + +original note""" + + assert_equal(np.ma.core.doc_note(method.__doc__, "note"), expected_doc) diff --git a/numpy/matrixlib/defmatrix.py b/numpy/matrixlib/defmatrix.py index a9ee74a5b..a414ee9bb 100644 --- a/numpy/matrixlib/defmatrix.py +++ b/numpy/matrixlib/defmatrix.py @@ -329,7 +329,7 @@ class matrix(N.ndarray): Parameters ---------- axis : None or int or tuple of ints, optional - Selects a subset of the single-dimensional entries in the shape. + Selects a subset of the axes of length one in the shape. If an axis is selected with shape entry greater than one, an error is raised. diff --git a/numpy/matrixlib/tests/test_masked_matrix.py b/numpy/matrixlib/tests/test_masked_matrix.py index 45424ecf0..95d3f44b6 100644 --- a/numpy/matrixlib/tests/test_masked_matrix.py +++ b/numpy/matrixlib/tests/test_masked_matrix.py @@ -1,4 +1,5 @@ import numpy as np +from numpy.testing import assert_warns from numpy.ma.testutils import (assert_, assert_equal, assert_raises, assert_array_equal) from numpy.ma.core import (masked_array, masked_values, masked, allequal, @@ -198,7 +199,8 @@ class TestSubclassing: # Result should work assert_equal(add(mx, x), mx+x) assert_(isinstance(add(mx, mx)._data, np.matrix)) - assert_(isinstance(add.outer(mx, mx), MMatrix)) + with assert_warns(DeprecationWarning): + assert_(isinstance(add.outer(mx, mx), MMatrix)) assert_(isinstance(hypot(mx, mx), MMatrix)) assert_(isinstance(hypot(mx, x), MMatrix)) diff --git a/numpy/polynomial/__init__.py b/numpy/polynomial/__init__.py index 43b2caba3..c832094e2 100644 --- a/numpy/polynomial/__init__.py +++ b/numpy/polynomial/__init__.py @@ -12,6 +12,107 @@ all operations on polynomials, including evaluation at an argument, are implemented as operations on the coefficients. Additional (module-specific) information can be found in the docstring for the module of interest. +This package provides *convenience classes* for each of six different kinds +of polynomials: + + ============ ================ + **Name** **Provides** + ============ ================ + Polynomial Power series + Chebyshev Chebyshev series + Legendre Legendre series + Laguerre Laguerre series + Hermite Hermite series + HermiteE HermiteE series + ============ ================ + +These *convenience classes* provide a consistent interface for creating, +manipulating, and fitting data with polynomials of different bases. +The convenience classes are the preferred interface for the `~numpy.polynomial` +package, and are available from the `numpy.polynomial` namespace. +This eliminates the need to +navigate to the corresponding submodules, e.g. ``np.polynomial.Polynomial`` +or ``np.polynomial.Chebyshev`` instead of +``np.polynomial.polynomial.Polynomial`` or +``np.polynomial.chebyshev.Chebyshev``, respectively. +The classes provide a more consistent and concise interface than the +type-specific functions defined in the submodules for each type of polynomial. +For example, to fit a Chebyshev polynomial with degree ``1`` to data given +by arrays ``xdata`` and ``ydata``, the +`~chebyshev.Chebyshev.fit` class method:: + + >>> from numpy.polynomial import Chebyshev + >>> c = Chebyshev.fit(xdata, ydata, deg=1) + +is preferred over the `chebyshev.chebfit` function from the +`numpy.polynomial.chebyshev` module:: + + >>> from numpy.polynomial.chebyshev import chebfit + >>> c = chebfit(xdata, ydata, deg=1) + +See :doc:`routines.polynomials.classes` for more details. + +Convenience Classes +=================== + +The following lists the various constants and methods common to all of +the classes representing the various kinds of polynomials. In the following, +the term ``Poly`` represents any one of the convenience classes (e.g. +``Polynomial``, ``Chebyshev``, ``Hermite``, etc.) while the lowercase ``p`` +represents an **instance** of a polynomial class. + +Constants +--------- + +- ``Poly.domain`` -- Default domain +- ``Poly.window`` -- Default window +- ``Poly.basis_name`` -- String used to represent the basis +- ``Poly.maxpower`` -- Maximum value ``n`` such that ``p**n`` is allowed +- ``Poly.nickname`` -- String used in printing + +Creation +-------- + +Methods for creating polynomial instances. + +- ``Poly.basis(degree)`` -- Basis polynomial of given degree +- ``Poly.identity()`` -- ``p`` where ``p(x) = x`` for all ``x`` +- ``Poly.fit(x, y, deg)`` -- ``p`` of degree ``deg`` with coefficients + determined by the least-squares fit to the data ``x``, ``y`` +- ``Poly.fromroots(roots)`` -- ``p`` with specified roots +- ``p.copy()`` -- Create a copy of ``p`` + +Conversion +---------- + +Methods for converting a polynomial instance of one kind to another. + +- ``p.cast(Poly)`` -- Convert ``p`` to instance of kind ``Poly`` +- ``p.convert(Poly)`` -- Convert ``p`` to instance of kind ``Poly`` or map + between ``domain`` and ``window`` + +Calculus +-------- +- ``p.deriv()`` -- Take the derivative of ``p`` +- ``p.integ()`` -- Integrate ``p`` + +Validation +---------- +- ``Poly.has_samecoef(p1, p2)`` -- Check if coefficients match +- ``Poly.has_samedomain(p1, p2)`` -- Check if domains match +- ``Poly.has_sametype(p1, p2)`` -- Check if types match +- ``Poly.has_samewindow(p1, p2)`` -- Check if windows match + +Misc +---- +- ``p.linspace()`` -- Return ``x, p(x)`` at equally-spaced points in ``domain`` +- ``p.mapparms()`` -- Return the parameters for the linear mapping between + ``domain`` and ``window``. +- ``p.roots()`` -- Return the roots of `p`. +- ``p.trim()`` -- Remove trailing coefficients. +- ``p.cutdeg(degree)`` -- Truncate p to given degree +- ``p.truncate(size)`` -- Truncate p to given size + """ from .polynomial import Polynomial from .chebyshev import Chebyshev diff --git a/numpy/polynomial/_polybase.py b/numpy/polynomial/_polybase.py index 30887b670..f4a67a222 100644 --- a/numpy/polynomial/_polybase.py +++ b/numpy/polynomial/_polybase.py @@ -111,11 +111,6 @@ class ABCPolyBase(abc.ABC): @property @abc.abstractmethod - def nickname(self): - pass - - @property - @abc.abstractmethod def basis_name(self): pass @@ -919,10 +914,8 @@ class ABCPolyBase(abc.ABC): ---------- x : array_like, shape (M,) x-coordinates of the M sample points ``(x[i], y[i])``. - y : array_like, shape (M,) or (M, K) - y-coordinates of the sample points. Several data sets of sample - points sharing the same x-coordinates can be fitted at once by - passing in a 2D-array that contains one dataset per column. + y : array_like, shape (M,) + y-coordinates of the M sample points ``(x[i], y[i])``. deg : int or 1-D array_like Degree(s) of the fitting polynomials. If `deg` is a single integer all terms up to and including the `deg`'th term are included in the diff --git a/numpy/polynomial/chebyshev.py b/numpy/polynomial/chebyshev.py index 431617414..d99fd98f5 100644 --- a/numpy/polynomial/chebyshev.py +++ b/numpy/polynomial/chebyshev.py @@ -2058,7 +2058,6 @@ class Chebyshev(ABCPolyBase): return cls(coef, domain=domain) # Virtual properties - nickname = 'cheb' domain = np.array(chebdomain) window = np.array(chebdomain) basis_name = 'T' diff --git a/numpy/polynomial/hermite.py b/numpy/polynomial/hermite.py index 487d8dfdb..280cad39e 100644 --- a/numpy/polynomial/hermite.py +++ b/numpy/polynomial/hermite.py @@ -1675,7 +1675,6 @@ class Hermite(ABCPolyBase): _fromroots = staticmethod(hermfromroots) # Virtual properties - nickname = 'herm' domain = np.array(hermdomain) window = np.array(hermdomain) basis_name = 'H' diff --git a/numpy/polynomial/hermite_e.py b/numpy/polynomial/hermite_e.py index cbec15184..9b3b25105 100644 --- a/numpy/polynomial/hermite_e.py +++ b/numpy/polynomial/hermite_e.py @@ -1669,7 +1669,6 @@ class HermiteE(ABCPolyBase): _fromroots = staticmethod(hermefromroots) # Virtual properties - nickname = 'herme' domain = np.array(hermedomain) window = np.array(hermedomain) basis_name = 'He' diff --git a/numpy/polynomial/laguerre.py b/numpy/polynomial/laguerre.py index 5b66d943e..c1db13215 100644 --- a/numpy/polynomial/laguerre.py +++ b/numpy/polynomial/laguerre.py @@ -132,10 +132,9 @@ def poly2lag(pol): """ [pol] = pu.as_series([pol]) - deg = len(pol) - 1 res = 0 - for i in range(deg, -1, -1): - res = lagadd(lagmulx(res), pol[i]) + for p in pol[::-1]: + res = lagadd(lagmulx(res), p) return res @@ -1626,7 +1625,6 @@ class Laguerre(ABCPolyBase): _fromroots = staticmethod(lagfromroots) # Virtual properties - nickname = 'lag' domain = np.array(lagdomain) window = np.array(lagdomain) basis_name = 'L' diff --git a/numpy/polynomial/legendre.py b/numpy/polynomial/legendre.py index 47e47a7b6..7b5b665f2 100644 --- a/numpy/polynomial/legendre.py +++ b/numpy/polynomial/legendre.py @@ -1642,7 +1642,6 @@ class Legendre(ABCPolyBase): _fromroots = staticmethod(legfromroots) # Virtual properties - nickname = 'leg' domain = np.array(legdomain) window = np.array(legdomain) basis_name = 'P' diff --git a/numpy/polynomial/polynomial.py b/numpy/polynomial/polynomial.py index 97f1e7dc0..83693441f 100644 --- a/numpy/polynomial/polynomial.py +++ b/numpy/polynomial/polynomial.py @@ -1490,7 +1490,6 @@ class Polynomial(ABCPolyBase): _fromroots = staticmethod(polyfromroots) # Virtual properties - nickname = 'poly' domain = np.array(polydomain) window = np.array(polydomain) basis_name = None diff --git a/numpy/polynomial/polyutils.py b/numpy/polynomial/polyutils.py index b1cf07e8a..d81ee9754 100644 --- a/numpy/polynomial/polyutils.py +++ b/numpy/polynomial/polyutils.py @@ -176,12 +176,12 @@ def as_series(alist, trim=True): arrays = [np.array(a, ndmin=1, copy=False) for a in alist] if min([a.size for a in arrays]) == 0: raise ValueError("Coefficient array is empty") - if any([a.ndim != 1 for a in arrays]): + if any(a.ndim != 1 for a in arrays): raise ValueError("Coefficient array is not 1-d") if trim: arrays = [trimseq(a) for a in arrays] - if any([a.dtype == np.dtype(object) for a in arrays]): + if any(a.dtype == np.dtype(object) for a in arrays): ret = [] for a in arrays: if a.dtype != np.dtype(object): diff --git a/numpy/py.typed b/numpy/py.typed new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/numpy/py.typed diff --git a/numpy/random/_common.pxd b/numpy/random/_common.pxd index 588f613ae..4f404b7a1 100644 --- a/numpy/random/_common.pxd +++ b/numpy/random/_common.pxd @@ -77,6 +77,8 @@ cdef object wrap_int(object val, object bits) cdef np.ndarray int_to_array(object value, object name, object bits, object uint_size) +cdef validate_output_shape(iter_shape, np.ndarray output) + cdef object cont(void *func, void *state, object size, object lock, int narg, object a, object a_name, constraint_type a_constraint, object b, object b_name, constraint_type b_constraint, diff --git a/numpy/random/_common.pyx b/numpy/random/_common.pyx index ef1afac7c..6d77aed03 100644 --- a/numpy/random/_common.pyx +++ b/numpy/random/_common.pyx @@ -218,6 +218,20 @@ cdef np.ndarray int_to_array(object value, object name, object bits, object uint return out +cdef validate_output_shape(iter_shape, np.ndarray output): + cdef np.npy_intp *shape + cdef ndim, i + cdef bint error + dims = np.PyArray_DIMS(output) + ndim = np.PyArray_NDIM(output) + output_shape = tuple((dims[i] for i in range(ndim))) + if iter_shape != output_shape: + raise ValueError( + f"Output size {output_shape} is not compatible with broadcast " + f"dimensions of inputs {iter_shape}." + ) + + cdef check_output(object out, object dtype, object size): if out is None: return @@ -404,6 +418,7 @@ cdef object cont_broadcast_1(void *func, void *state, object size, object lock, randoms_data = <double *>np.PyArray_DATA(randoms) n = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew2(randoms, a_arr) + validate_output_shape(it.shape, randoms) with lock, nogil: for i in range(n): @@ -441,6 +456,8 @@ cdef object cont_broadcast_2(void *func, void *state, object size, object lock, n = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew3(randoms, a_arr, b_arr) + validate_output_shape(it.shape, randoms) + with lock, nogil: for i in range(n): a_val = (<double*>np.PyArray_MultiIter_DATA(it, 1))[0] @@ -482,6 +499,8 @@ cdef object cont_broadcast_3(void *func, void *state, object size, object lock, n = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew4(randoms, a_arr, b_arr, c_arr) + validate_output_shape(it.shape, randoms) + with lock, nogil: for i in range(n): a_val = (<double*>np.PyArray_MultiIter_DATA(it, 1))[0] @@ -611,6 +630,8 @@ cdef object discrete_broadcast_d(void *func, void *state, object size, object lo n = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew2(randoms, a_arr) + validate_output_shape(it.shape, randoms) + with lock, nogil: for i in range(n): a_val = (<double*>np.PyArray_MultiIter_DATA(it, 1))[0] @@ -645,6 +666,8 @@ cdef object discrete_broadcast_dd(void *func, void *state, object size, object l n = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew3(randoms, a_arr, b_arr) + validate_output_shape(it.shape, randoms) + with lock, nogil: for i in range(n): a_val = (<double*>np.PyArray_MultiIter_DATA(it, 1))[0] @@ -680,6 +703,8 @@ cdef object discrete_broadcast_di(void *func, void *state, object size, object l n = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew3(randoms, a_arr, b_arr) + validate_output_shape(it.shape, randoms) + with lock, nogil: for i in range(n): a_val = (<double*>np.PyArray_MultiIter_DATA(it, 1))[0] @@ -719,6 +744,8 @@ cdef object discrete_broadcast_iii(void *func, void *state, object size, object n = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew4(randoms, a_arr, b_arr, c_arr) + validate_output_shape(it.shape, randoms) + with lock, nogil: for i in range(n): a_val = (<int64_t*>np.PyArray_MultiIter_DATA(it, 1))[0] @@ -750,6 +777,8 @@ cdef object discrete_broadcast_i(void *func, void *state, object size, object lo n = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew2(randoms, a_arr) + validate_output_shape(it.shape, randoms) + with lock, nogil: for i in range(n): a_val = (<int64_t*>np.PyArray_MultiIter_DATA(it, 1))[0] @@ -923,6 +952,7 @@ cdef object cont_broadcast_1_f(void *func, bitgen_t *state, object size, object randoms_data = <float *>np.PyArray_DATA(randoms) n = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew2(randoms, a_arr) + validate_output_shape(it.shape, randoms) with lock, nogil: for i in range(n): diff --git a/numpy/random/_generator.pyx b/numpy/random/_generator.pyx index 2e54dce5f..66847043b 100644 --- a/numpy/random/_generator.pyx +++ b/numpy/random/_generator.pyx @@ -25,6 +25,7 @@ from ._common cimport (POISSON_LAM_MAX, CONS_POSITIVE, CONS_NONE, CONS_GT_1, CONS_POSITIVE_NOT_NAN, CONS_POISSON, double_fill, cont, kahan_sum, cont_broadcast_3, float_fill, cont_f, check_array_constraint, check_constraint, disc, discrete_broadcast_iii, + validate_output_shape ) np.import_array() @@ -2779,7 +2780,7 @@ cdef class Generator: generate zero positive results. >>> sum(rng.binomial(9, 0.1, 20000) == 0)/20000. - # answer = 0.38885, or 38%. + # answer = 0.38885, or 39%. """ @@ -2809,6 +2810,7 @@ cdef class Generator: cnt = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew3(randoms, p_arr, n_arr) + validate_output_shape(it.shape, randoms) with self.lock, nogil: for i in range(cnt): _dp = (<double*>np.PyArray_MultiIter_DATA(it, 1))[0] @@ -3345,7 +3347,8 @@ cdef class Generator: def multivariate_normal(self, mean, cov, size=None, check_valid='warn', tol=1e-8, *, method='svd'): """ - multivariate_normal(mean, cov, size=None, check_valid='warn', tol=1e-8) + multivariate_normal(mean, cov, size=None, check_valid='warn', + tol=1e-8, *, method='svd') Draw random samples from a multivariate normal distribution. @@ -3605,7 +3608,7 @@ cdef class Generator: Now, do one experiment throwing the dice 10 time, and 10 times again, and another throwing the dice 20 times, and 20 times again: - >>> rng.multinomial([[10], [20]], [1/6.]*6, size=2) + >>> rng.multinomial([[10], [20]], [1/6.]*6, size=(2, 2)) array([[[2, 4, 0, 1, 2, 1], [1, 3, 0, 3, 1, 2]], [[1, 4, 4, 4, 4, 3], @@ -3660,6 +3663,7 @@ cdef class Generator: temp = np.empty(size, dtype=np.int8) temp_arr = <np.ndarray>temp it = np.PyArray_MultiIterNew2(on, temp_arr) + validate_output_shape(it.shape, temp_arr) shape = it.shape + (d,) multin = np.zeros(shape, dtype=np.int64) mnarr = <np.ndarray>multin @@ -4348,6 +4352,59 @@ def default_rng(seed=None): ----- If ``seed`` is not a `BitGenerator` or a `Generator`, a new `BitGenerator` is instantiated. This function does not manage a default global instance. + + Examples + -------- + ``default_rng`` is the reccomended constructor for the random number class + ``Generator``. Here are several ways we can construct a random + number generator using ``default_rng`` and the ``Generator`` class. + + Here we use ``default_rng`` to generate a random float: + + >>> import numpy as np + >>> rng = np.random.default_rng(12345) + >>> print(rng) + Generator(PCG64) + >>> rfloat = rng.random() + >>> rfloat + 0.22733602246716966 + >>> type(rfloat) + <class 'float'> + + Here we use ``default_rng`` to generate 3 random integers between 0 + (inclusive) and 10 (exclusive): + + >>> import numpy as np + >>> rng = np.random.default_rng(12345) + >>> rints = rng.integers(low=0, high=10, size=3) + >>> rints + array([6, 2, 7]) + >>> type(rints[0]) + <class 'numpy.int64'> + + Here we specify a seed so that we have reproducible results: + + >>> import numpy as np + >>> rng = np.random.default_rng(seed=42) + >>> print(rng) + Generator(PCG64) + >>> arr1 = rng.random((3, 3)) + >>> arr1 + array([[0.77395605, 0.43887844, 0.85859792], + [0.69736803, 0.09417735, 0.97562235], + [0.7611397 , 0.78606431, 0.12811363]]) + + If we exit and restart our Python interpreter, we'll see that we + generate the same random numbers again: + + >>> import numpy as np + >>> rng = np.random.default_rng(seed=42) + >>> arr2 = rng.random((3, 3)) + >>> arr2 + array([[0.77395605, 0.43887844, 0.85859792], + [0.69736803, 0.09417735, 0.97562235], + [0.7611397 , 0.78606431, 0.12811363]]) + """ if _check_bit_generator(seed): # We were passed a BitGenerator, so just wrap it up. diff --git a/numpy/random/bit_generator.pyx b/numpy/random/bit_generator.pyx index f145ec13d..9b0c363ef 100644 --- a/numpy/random/bit_generator.pyx +++ b/numpy/random/bit_generator.pyx @@ -382,13 +382,22 @@ cdef class SeedSequence(): ------- entropy_array : 1D uint32 array """ - # Convert run-entropy, program-entropy, and the spawn key into uint32 + # Convert run-entropy and the spawn key into uint32 # arrays and concatenate them. # We MUST have at least some run-entropy. The others are optional. assert self.entropy is not None run_entropy = _coerce_to_uint32_array(self.entropy) spawn_entropy = _coerce_to_uint32_array(self.spawn_key) + if len(spawn_entropy) > 0 and len(run_entropy) < self.pool_size: + # Explicitly fill out the entropy with 0s to the pool size to avoid + # conflict with spawn keys. We changed this in 1.19.0 to fix + # gh-16539. In order to preserve stream-compatibility with + # unspawned SeedSequences with small entropy inputs, we only do + # this when a spawn_key is specified. + diff = self.pool_size - len(run_entropy) + run_entropy = np.concatenate( + [run_entropy, np.zeros(diff, dtype=np.uint32)]) entropy_array = np.concatenate([run_entropy, spawn_entropy]) return entropy_array @@ -498,7 +507,7 @@ cdef class BitGenerator(): lock. See Also - ------- + -------- SeedSequence """ diff --git a/numpy/random/mtrand.pyx b/numpy/random/mtrand.pyx index 8820a6e09..df305e689 100644 --- a/numpy/random/mtrand.pyx +++ b/numpy/random/mtrand.pyx @@ -22,6 +22,7 @@ from ._common cimport (POISSON_LAM_MAX, CONS_POSITIVE, CONS_NONE, CONS_GT_1, LEGACY_CONS_POISSON, double_fill, cont, kahan_sum, cont_broadcast_3, check_array_constraint, check_constraint, disc, discrete_broadcast_iii, + validate_output_shape ) cdef extern from "numpy/random/distributions.h": @@ -3374,6 +3375,7 @@ cdef class RandomState: cnt = np.PyArray_SIZE(randoms) it = np.PyArray_MultiIterNew3(randoms, p_arr, n_arr) + validate_output_shape(it.shape, randoms) with self.lock, nogil: for i in range(cnt): _dp = (<double*>np.PyArray_MultiIter_DATA(it, 1))[0] diff --git a/numpy/random/tests/test_extending.py b/numpy/random/tests/test_extending.py index 77353463e..99a819efb 100644 --- a/numpy/random/tests/test_extending.py +++ b/numpy/random/tests/test_extending.py @@ -31,11 +31,11 @@ except ImportError: cython = None else: from distutils.version import LooseVersion - # Cython 0.29.14 is required for Python 3.8 and there are + # Cython 0.29.21 is required for Python 3.9 and there are # other fixes in the 0.29 series that are needed even for earlier # Python versions. # Note: keep in sync with the one in pyproject.toml - required_version = LooseVersion('0.29.14') + required_version = LooseVersion('0.29.21') if LooseVersion(cython_version) < required_version: # too old or wrong cython, skip the test cython = None diff --git a/numpy/random/tests/test_generator_mt19937.py b/numpy/random/tests/test_generator_mt19937.py index f72b748ba..bb6d25ef1 100644 --- a/numpy/random/tests/test_generator_mt19937.py +++ b/numpy/random/tests/test_generator_mt19937.py @@ -2397,3 +2397,54 @@ def test_jumped(config): md5 = hashlib.md5(key) assert jumped.state["state"]["pos"] == config["jumped"]["pos"] assert md5.hexdigest() == config["jumped"]["key_md5"] + + +def test_broadcast_size_error(): + mu = np.ones(3) + sigma = np.ones((4, 3)) + size = (10, 4, 2) + assert random.normal(mu, sigma, size=(5, 4, 3)).shape == (5, 4, 3) + with pytest.raises(ValueError): + random.normal(mu, sigma, size=size) + with pytest.raises(ValueError): + random.normal(mu, sigma, size=(1, 3)) + with pytest.raises(ValueError): + random.normal(mu, sigma, size=(4, 1, 1)) + # 1 arg + shape = np.ones((4, 3)) + with pytest.raises(ValueError): + random.standard_gamma(shape, size=size) + with pytest.raises(ValueError): + random.standard_gamma(shape, size=(3,)) + with pytest.raises(ValueError): + random.standard_gamma(shape, size=3) + # Check out + out = np.empty(size) + with pytest.raises(ValueError): + random.standard_gamma(shape, out=out) + + # 2 arg + with pytest.raises(ValueError): + random.binomial(1, [0.3, 0.7], size=(2, 1)) + with pytest.raises(ValueError): + random.binomial([1, 2], 0.3, size=(2, 1)) + with pytest.raises(ValueError): + random.binomial([1, 2], [0.3, 0.7], size=(2, 1)) + with pytest.raises(ValueError): + random.multinomial([2, 2], [.3, .7], size=(2, 1)) + + # 3 arg + a = random.chisquare(5, size=3) + b = random.chisquare(5, size=(4, 3)) + c = random.chisquare(5, size=(5, 4, 3)) + assert random.noncentral_f(a, b, c).shape == (5, 4, 3) + with pytest.raises(ValueError, match=r"Output size \(6, 5, 1, 1\) is"): + random.noncentral_f(a, b, c, size=(6, 5, 1, 1)) + + +def test_broadcast_size_scalar(): + mu = np.ones(3) + sigma = np.ones(3) + random.normal(mu, sigma, size=3) + with pytest.raises(ValueError): + random.normal(mu, sigma, size=2) diff --git a/numpy/random/tests/test_randomstate.py b/numpy/random/tests/test_randomstate.py index edd7811bf..23dbbed6a 100644 --- a/numpy/random/tests/test_randomstate.py +++ b/numpy/random/tests/test_randomstate.py @@ -1989,3 +1989,13 @@ def test_integer_repeat(int_func): val = val.byteswap() res = hashlib.md5(val.view(np.int8)).hexdigest() assert_(res == md5) + + +def test_broadcast_size_error(): + # GH-16833 + with pytest.raises(ValueError): + random.binomial(1, [0.3, 0.7], size=(2, 1)) + with pytest.raises(ValueError): + random.binomial([1, 2], 0.3, size=(2, 1)) + with pytest.raises(ValueError): + random.binomial([1, 2], [0.3, 0.7], size=(2, 1)) diff --git a/numpy/random/tests/test_seed_sequence.py b/numpy/random/tests/test_seed_sequence.py index fe23680ed..f08cf80fa 100644 --- a/numpy/random/tests/test_seed_sequence.py +++ b/numpy/random/tests/test_seed_sequence.py @@ -1,5 +1,5 @@ import numpy as np -from numpy.testing import assert_array_equal +from numpy.testing import assert_array_equal, assert_array_compare from numpy.random import SeedSequence @@ -52,3 +52,29 @@ def test_reference_data(): assert_array_equal(state, expected) state64 = ss.generate_state(len(expected64), dtype=np.uint64) assert_array_equal(state64, expected64) + + +def test_zero_padding(): + """ Ensure that the implicit zero-padding does not cause problems. + """ + # Ensure that large integers are inserted in little-endian fashion to avoid + # trailing 0s. + ss0 = SeedSequence(42) + ss1 = SeedSequence(42 << 32) + assert_array_compare( + np.not_equal, + ss0.generate_state(4), + ss1.generate_state(4)) + + # Ensure backwards compatibility with the original 0.17 release for small + # integers and no spawn key. + expected42 = np.array([3444837047, 2669555309, 2046530742, 3581440988], + dtype=np.uint32) + assert_array_equal(SeedSequence(42).generate_state(4), expected42) + + # Regression test for gh-16539 to ensure that the implicit 0s don't + # conflict with spawn keys. + assert_array_compare( + np.not_equal, + SeedSequence(42, spawn_key=(0,)).generate_state(4), + expected42) diff --git a/numpy/setup.py b/numpy/setup.py index 52db6a68b..cbf633504 100644 --- a/numpy/setup.py +++ b/numpy/setup.py @@ -17,7 +17,10 @@ def configuration(parent_package='',top_path=None): config.add_subpackage('polynomial') config.add_subpackage('random') config.add_subpackage('testing') + config.add_subpackage('typing') config.add_data_dir('doc') + config.add_data_files('py.typed') + config.add_data_files('*.pyi') config.add_subpackage('tests') config.make_config_py() # installs __config__.py return config diff --git a/numpy/testing/_private/utils.py b/numpy/testing/_private/utils.py index ef623255b..3827b7505 100644 --- a/numpy/testing/_private/utils.py +++ b/numpy/testing/_private/utils.py @@ -719,6 +719,8 @@ def assert_array_compare(comparison, x, y, err_msg='', verbose=True, at the same locations. """ + __tracebackhide__ = True # Hide traceback for py.test + x_id = func(x) y_id = func(y) # We include work-arounds here to handle three types of slightly diff --git a/numpy/testing/tests/test_utils.py b/numpy/testing/tests/test_utils.py index b899e94f4..6a6cc664a 100644 --- a/numpy/testing/tests/test_utils.py +++ b/numpy/testing/tests/test_utils.py @@ -941,6 +941,17 @@ class TestArrayAlmostEqualNulp: assert_raises(AssertionError, assert_array_almost_equal_nulp, x, y, nulp) + def test_float64_ignore_nan(self): + # Ignore ULP differences between various NAN's + # Note that MIPS may reverse quiet and signaling nans + # so we use the builtin version as a base. + offset = np.uint64(0xffffffff) + nan1_i64 = np.array(np.nan, dtype=np.float64).view(np.uint64) + nan2_i64 = nan1_i64 ^ offset # nan payload on MIPS is all ones. + nan1_f64 = nan1_i64.view(np.float64) + nan2_f64 = nan2_i64.view(np.float64) + assert_array_max_ulp(nan1_f64, nan2_f64, 0) + def test_float32_pass(self): nulp = 5 x = np.linspace(-20, 20, 50, dtype=np.float32) @@ -971,6 +982,17 @@ class TestArrayAlmostEqualNulp: assert_raises(AssertionError, assert_array_almost_equal_nulp, x, y, nulp) + def test_float32_ignore_nan(self): + # Ignore ULP differences between various NAN's + # Note that MIPS may reverse quiet and signaling nans + # so we use the builtin version as a base. + offset = np.uint32(0xffff) + nan1_i32 = np.array(np.nan, dtype=np.float32).view(np.uint32) + nan2_i32 = nan1_i32 ^ offset # nan payload on MIPS is all ones. + nan1_f32 = nan1_i32.view(np.float32) + nan2_f32 = nan2_i32.view(np.float32) + assert_array_max_ulp(nan1_f32, nan2_f32, 0) + def test_float16_pass(self): nulp = 5 x = np.linspace(-4, 4, 10, dtype=np.float16) @@ -1001,6 +1023,17 @@ class TestArrayAlmostEqualNulp: assert_raises(AssertionError, assert_array_almost_equal_nulp, x, y, nulp) + def test_float16_ignore_nan(self): + # Ignore ULP differences between various NAN's + # Note that MIPS may reverse quiet and signaling nans + # so we use the builtin version as a base. + offset = np.uint16(0xff) + nan1_i16 = np.array(np.nan, dtype=np.float16).view(np.uint16) + nan2_i16 = nan1_i16 ^ offset # nan payload on MIPS is all ones. + nan1_f16 = nan1_i16.view(np.float16) + nan2_f16 = nan2_i16.view(np.float16) + assert_array_max_ulp(nan1_f16, nan2_f16, 0) + def test_complex128_pass(self): nulp = 5 x = np.linspace(-20, 20, 50, dtype=np.float64) diff --git a/numpy/tests/setup.py b/numpy/tests/setup.py new file mode 100644 index 000000000..f034cdf95 --- /dev/null +++ b/numpy/tests/setup.py @@ -0,0 +1,10 @@ +def configuration(parent_package='', top_path=None): + from numpy.distutils.misc_util import Configuration + config = Configuration('tests', parent_package, top_path) + config.add_data_dir('typing') + return config + + +if __name__ == '__main__': + from numpy.distutils.core import setup + setup(configuration=configuration) diff --git a/numpy/tests/test_public_api.py b/numpy/tests/test_public_api.py index 7ce74bc43..a9d6da01c 100644 --- a/numpy/tests/test_public_api.py +++ b/numpy/tests/test_public_api.py @@ -54,22 +54,26 @@ def test_numpy_namespace(): 'show_config': 'numpy.__config__.show', 'who': 'numpy.lib.utils.who', } - # These built-in types are re-exported by numpy. - builtins = { - 'bool': 'builtins.bool', - 'complex': 'builtins.complex', - 'float': 'builtins.float', - 'int': 'builtins.int', - 'long': 'builtins.int', - 'object': 'builtins.object', - 'str': 'builtins.str', - 'unicode': 'builtins.str', - } - whitelist = dict(undocumented, **builtins) + if sys.version_info < (3, 7): + # These built-in types are re-exported by numpy. + builtins = { + 'bool': 'builtins.bool', + 'complex': 'builtins.complex', + 'float': 'builtins.float', + 'int': 'builtins.int', + 'long': 'builtins.int', + 'object': 'builtins.object', + 'str': 'builtins.str', + 'unicode': 'builtins.str', + } + allowlist = dict(undocumented, **builtins) + else: + # after 3.7, we override dir to not show these members + allowlist = undocumented bad_results = check_dir(np) # pytest gives better error messages with the builtin assert than with # assert_equal - assert bad_results == whitelist + assert bad_results == allowlist @pytest.mark.parametrize('name', ['testing', 'Tester']) @@ -98,7 +102,7 @@ def test_dir_testing(): """Assert that output of dir has only one "testing/tester" attribute without duplicate""" assert len(dir(np)) == len(set(dir(np))) - + def test_numpy_linalg(): bad_results = check_dir(np.linalg) @@ -176,6 +180,7 @@ PUBLIC_MODULES = ['numpy.' + s for s in [ "polynomial.polyutils", "random", "testing", + "typing", "version", ]] @@ -209,6 +214,7 @@ PRIVATE_BUT_PRESENT_MODULES = ['numpy.' + s for s in [ "core.umath", "core.umath_tests", "distutils.ccompiler", + 'distutils.ccompiler_opt', "distutils.command", "distutils.command.autodist", "distutils.command.bdist_rpm", diff --git a/numpy/tests/test_typing.py b/numpy/tests/test_typing.py new file mode 100644 index 000000000..04ea3c64d --- /dev/null +++ b/numpy/tests/test_typing.py @@ -0,0 +1,142 @@ +import importlib.util +import itertools +import os +import re +from collections import defaultdict + +import pytest +try: + from mypy import api +except ImportError: + NO_MYPY = True +else: + NO_MYPY = False + +TESTS_DIR = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "typing", +) +PASS_DIR = os.path.join(TESTS_DIR, "pass") +FAIL_DIR = os.path.join(TESTS_DIR, "fail") +REVEAL_DIR = os.path.join(TESTS_DIR, "reveal") +MYPY_INI = os.path.join(TESTS_DIR, "mypy.ini") +CACHE_DIR = os.path.join(TESTS_DIR, ".mypy_cache") + + +def get_test_cases(directory): + for root, _, files in os.walk(directory): + for fname in files: + if os.path.splitext(fname)[-1] == ".py": + fullpath = os.path.join(root, fname) + # Use relative path for nice py.test name + relpath = os.path.relpath(fullpath, start=directory) + + yield pytest.param( + fullpath, + # Manually specify a name for the test + id=relpath, + ) + + +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed") +@pytest.mark.parametrize("path", get_test_cases(PASS_DIR)) +def test_success(path): + stdout, stderr, exitcode = api.run([ + "--config-file", + MYPY_INI, + "--cache-dir", + CACHE_DIR, + path, + ]) + assert exitcode == 0, stdout + assert re.match(r"Success: no issues found in \d+ source files?", stdout.strip()) + + +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed") +@pytest.mark.parametrize("path", get_test_cases(FAIL_DIR)) +def test_fail(path): + stdout, stderr, exitcode = api.run([ + "--config-file", + MYPY_INI, + "--cache-dir", + CACHE_DIR, + path, + ]) + assert exitcode != 0 + + with open(path) as fin: + lines = fin.readlines() + + errors = defaultdict(lambda: "") + error_lines = stdout.rstrip("\n").split("\n") + assert re.match( + r"Found \d+ errors? in \d+ files? \(checked \d+ source files?\)", + error_lines[-1].strip(), + ) + for error_line in error_lines[:-1]: + error_line = error_line.strip() + if not error_line: + continue + + match = re.match( + r"^.+\.py:(?P<lineno>\d+): (error|note): .+$", + error_line, + ) + if match is None: + raise ValueError(f"Unexpected error line format: {error_line}") + lineno = int(match.group('lineno')) + errors[lineno] += error_line + + for i, line in enumerate(lines): + lineno = i + 1 + if " E:" not in line and lineno not in errors: + continue + + target_line = lines[lineno - 1] + if "# E:" in target_line: + marker = target_line.split("# E:")[-1].strip() + assert lineno in errors, f'Extra error "{marker}"' + assert marker in errors[lineno] + else: + pytest.fail(f"Error {repr(errors[lineno])} not found") + + +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed") +@pytest.mark.parametrize("path", get_test_cases(REVEAL_DIR)) +def test_reveal(path): + stdout, stderr, exitcode = api.run([ + "--config-file", + MYPY_INI, + "--cache-dir", + CACHE_DIR, + path, + ]) + + with open(path) as fin: + lines = fin.readlines() + + for error_line in stdout.split("\n"): + error_line = error_line.strip() + if not error_line: + continue + + match = re.match( + r"^.+\.py:(?P<lineno>\d+): note: .+$", + error_line, + ) + if match is None: + raise ValueError(f"Unexpected reveal line format: {error_line}") + lineno = int(match.group('lineno')) + assert "Revealed type is" in error_line + marker = lines[lineno - 1].split("# E:")[-1].strip() + assert marker in error_line + + +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed") +@pytest.mark.parametrize("path", get_test_cases(PASS_DIR)) +def test_code_runs(path): + path_without_extension, _ = os.path.splitext(path) + dirname, filename = path.split(os.sep)[-2:] + spec = importlib.util.spec_from_file_location(f"{dirname}.{filename}", path) + test_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(test_module) diff --git a/numpy/tests/typing/fail/array_like.py b/numpy/tests/typing/fail/array_like.py new file mode 100644 index 000000000..a97e72dc7 --- /dev/null +++ b/numpy/tests/typing/fail/array_like.py @@ -0,0 +1,16 @@ +import numpy as np +from numpy.typing import ArrayLike + + +class A: + pass + + +x1: ArrayLike = (i for i in range(10)) # E: Incompatible types in assignment +x2: ArrayLike = A() # E: Incompatible types in assignment +x3: ArrayLike = {1: "foo", 2: "bar"} # E: Incompatible types in assignment + +scalar = np.int64(1) +scalar.__array__(dtype=np.float64) # E: Unexpected keyword argument +array = np.array([1]) +array.__array__(dtype=np.float64) # E: Unexpected keyword argument diff --git a/numpy/tests/typing/fail/fromnumeric.py b/numpy/tests/typing/fail/fromnumeric.py new file mode 100644 index 000000000..66f8a89d0 --- /dev/null +++ b/numpy/tests/typing/fail/fromnumeric.py @@ -0,0 +1,126 @@ +"""Tests for :mod:`numpy.core.fromnumeric`.""" + +import numpy as np + +A = np.array(True, ndmin=2, dtype=bool) +A.setflags(write=False) + +a = np.bool_(True) + +np.take(a, None) # E: No overload variant of "take" matches argument type +np.take(a, axis=1.0) # E: No overload variant of "take" matches argument type +np.take(A, out=1) # E: No overload variant of "take" matches argument type +np.take(A, mode="bob") # E: No overload variant of "take" matches argument type + +np.reshape(a, None) # E: Argument 2 to "reshape" has incompatible type +np.reshape(A, 1, order="bob") # E: Argument "order" to "reshape" has incompatible type + +np.choose(a, None) # E: No overload variant of "choose" matches argument type +np.choose(a, out=1.0) # E: No overload variant of "choose" matches argument type +np.choose(A, mode="bob") # E: No overload variant of "choose" matches argument type + +np.repeat(a, None) # E: Argument 2 to "repeat" has incompatible type +np.repeat(A, 1, axis=1.0) # E: Argument "axis" to "repeat" has incompatible type + +np.swapaxes(A, None, 1) # E: Argument 2 to "swapaxes" has incompatible type +np.swapaxes(A, 1, [0]) # E: Argument 3 to "swapaxes" has incompatible type + +np.transpose(A, axes=1.0) # E: Argument "axes" to "transpose" has incompatible type + +np.partition(a, None) # E: Argument 2 to "partition" has incompatible type +np.partition( + a, 0, axis="bob" # E: Argument "axis" to "partition" has incompatible type +) +np.partition( + A, 0, kind="bob" # E: Argument "kind" to "partition" has incompatible type +) +np.partition( + A, 0, order=range(5) # E: Argument "order" to "partition" has incompatible type +) + +np.argpartition( # E: No overload variant of "argpartition" matches argument type + a, None +) +np.argpartition( # E: No overload variant of "argpartition" matches argument type + a, 0, axis="bob" +) +np.argpartition( # E: No overload variant of "argpartition" matches argument type + A, 0, kind="bob" +) +np.argpartition( + A, 0, order=range(5) # E: Argument "order" to "argpartition" has incompatible type +) + +np.sort(A, axis="bob") # E: Argument "axis" to "sort" has incompatible type +np.sort(A, kind="bob") # E: Argument "kind" to "sort" has incompatible type +np.sort(A, order=range(5)) # E: Argument "order" to "sort" has incompatible type + +np.argsort(A, axis="bob") # E: Argument "axis" to "argsort" has incompatible type +np.argsort(A, kind="bob") # E: Argument "kind" to "argsort" has incompatible type +np.argsort(A, order=range(5)) # E: Argument "order" to "argsort" has incompatible type + +np.argmax(A, axis="bob") # E: No overload variant of "argmax" matches argument type +np.argmax(A, kind="bob") # E: No overload variant of "argmax" matches argument type + +np.argmin(A, axis="bob") # E: No overload variant of "argmin" matches argument type +np.argmin(A, kind="bob") # E: No overload variant of "argmin" matches argument type + +np.searchsorted( # E: No overload variant of "searchsorted" matches argument type + A[0], 0, side="bob" +) +np.searchsorted( # E: No overload variant of "searchsorted" matches argument type + A[0], 0, sorter=1.0 +) + +np.resize(A, 1.0) # E: Argument 2 to "resize" has incompatible type + +np.squeeze(A, 1.0) # E: No overload variant of "squeeze" matches argument type + +np.diagonal(A, offset=None) # E: Argument "offset" to "diagonal" has incompatible type +np.diagonal(A, axis1="bob") # E: Argument "axis1" to "diagonal" has incompatible type +np.diagonal(A, axis2=[]) # E: Argument "axis2" to "diagonal" has incompatible type + +np.trace(A, offset=None) # E: Argument "offset" to "trace" has incompatible type +np.trace(A, axis1="bob") # E: Argument "axis1" to "trace" has incompatible type +np.trace(A, axis2=[]) # E: Argument "axis2" to "trace" has incompatible type + +np.ravel(a, order="bob") # E: Argument "order" to "ravel" has incompatible type + +np.compress( + [True], A, axis=1.0 # E: Argument "axis" to "compress" has incompatible type +) + +np.clip(a, 1, 2, out=1) # E: No overload variant of "clip" matches argument type +np.clip(1, None, None) # E: No overload variant of "clip" matches argument type + +np.sum(a, axis=1.0) # E: No overload variant of "sum" matches argument type +np.sum(a, keepdims=1.0) # E: No overload variant of "sum" matches argument type +np.sum(a, initial=[1]) # E: No overload variant of "sum" matches argument type + +np.all(a, axis=1.0) # E: No overload variant of "all" matches argument type +np.all(a, keepdims=1.0) # E: No overload variant of "all" matches argument type +np.all(a, out=1.0) # E: No overload variant of "all" matches argument type + +np.any(a, axis=1.0) # E: No overload variant of "any" matches argument type +np.any(a, keepdims=1.0) # E: No overload variant of "any" matches argument type +np.any(a, out=1.0) # E: No overload variant of "any" matches argument type + +np.cumsum(a, axis=1.0) # E: Argument "axis" to "cumsum" has incompatible type +np.cumsum(a, dtype=1.0) # E: Argument "dtype" to "cumsum" has incompatible type +np.cumsum(a, out=1.0) # E: Argument "out" to "cumsum" has incompatible type + +np.ptp(a, axis=1.0) # E: No overload variant of "ptp" matches argument type +np.ptp(a, keepdims=1.0) # E: No overload variant of "ptp" matches argument type +np.ptp(a, out=1.0) # E: No overload variant of "ptp" matches argument type + +np.amax(a, axis=1.0) # E: No overload variant of "amax" matches argument type +np.amax(a, keepdims=1.0) # E: No overload variant of "amax" matches argument type +np.amax(a, out=1.0) # E: No overload variant of "amax" matches argument type +np.amax(a, initial=[1.0]) # E: No overload variant of "amax" matches argument type +np.amax(a, where=[1.0]) # E: List item 0 has incompatible type + +np.amin(a, axis=1.0) # E: No overload variant of "amin" matches argument type +np.amin(a, keepdims=1.0) # E: No overload variant of "amin" matches argument type +np.amin(a, out=1.0) # E: No overload variant of "amin" matches argument type +np.amin(a, initial=[1.0]) # E: No overload variant of "amin" matches argument type +np.amin(a, where=[1.0]) # E: List item 0 has incompatible type diff --git a/numpy/tests/typing/fail/ndarray.py b/numpy/tests/typing/fail/ndarray.py new file mode 100644 index 000000000..5a5130d40 --- /dev/null +++ b/numpy/tests/typing/fail/ndarray.py @@ -0,0 +1,11 @@ +import numpy as np + +# Ban setting dtype since mutating the type of the array in place +# makes having ndarray be generic over dtype impossible. Generally +# users should use `ndarray.view` in this situation anyway. See +# +# https://github.com/numpy/numpy-stubs/issues/7 +# +# for more context. +float_array = np.array([1.0]) +float_array.dtype = np.bool_ # E: Property "dtype" defined in "ndarray" is read-only diff --git a/numpy/tests/typing/fail/numerictypes.py b/numpy/tests/typing/fail/numerictypes.py new file mode 100644 index 000000000..dd03eacc1 --- /dev/null +++ b/numpy/tests/typing/fail/numerictypes.py @@ -0,0 +1,13 @@ +import numpy as np + +# Techincally this works, but probably shouldn't. See +# +# https://github.com/numpy/numpy/issues/16366 +# +np.maximum_sctype(1) # E: incompatible type "int" + +np.issubsctype(1, np.int64) # E: incompatible type "int" + +np.issubdtype(1, np.int64) # E: incompatible type "int" + +np.find_common_type(np.int64, np.int64) # E: incompatible type "Type[int64]" diff --git a/numpy/tests/typing/fail/scalars.py b/numpy/tests/typing/fail/scalars.py new file mode 100644 index 000000000..5d7221895 --- /dev/null +++ b/numpy/tests/typing/fail/scalars.py @@ -0,0 +1,81 @@ +import numpy as np + +# Construction + +np.float32(3j) # E: incompatible type + +# Technically the following examples are valid NumPy code. But they +# are not considered a best practice, and people who wish to use the +# stubs should instead do +# +# np.array([1.0, 0.0, 0.0], dtype=np.float32) +# np.array([], dtype=np.complex64) +# +# See e.g. the discussion on the mailing list +# +# https://mail.python.org/pipermail/numpy-discussion/2020-April/080566.html +# +# and the issue +# +# https://github.com/numpy/numpy-stubs/issues/41 +# +# for more context. +np.float32([1.0, 0.0, 0.0]) # E: incompatible type +np.complex64([]) # E: incompatible type + +np.complex64(1, 2) # E: Too many arguments +# TODO: protocols (can't check for non-existent protocols w/ __getattr__) + +np.datetime64(0) # E: non-matching overload + +dt_64 = np.datetime64(0, "D") +td_64 = np.timedelta64(1, "h") + +dt_64 + dt_64 # E: Unsupported operand types + +td_64 - dt_64 # E: Unsupported operand types +td_64 / dt_64 # E: No overload +td_64 % 1 # E: Unsupported operand types +td_64 % dt_64 # E: Unsupported operand types + + +class A: + def __float__(self): + return 1.0 + + +np.int8(A()) # E: incompatible type +np.int16(A()) # E: incompatible type +np.int32(A()) # E: incompatible type +np.int64(A()) # E: incompatible type +np.uint8(A()) # E: incompatible type +np.uint16(A()) # E: incompatible type +np.uint32(A()) # E: incompatible type +np.uint64(A()) # E: incompatible type + +np.void("test") # E: incompatible type + +np.generic(1) # E: Cannot instantiate abstract class +np.number(1) # E: Cannot instantiate abstract class +np.integer(1) # E: Cannot instantiate abstract class +np.signedinteger(1) # E: Cannot instantiate abstract class +np.unsignedinteger(1) # E: Cannot instantiate abstract class +np.inexact(1) # E: Cannot instantiate abstract class +np.floating(1) # E: Cannot instantiate abstract class +np.complexfloating(1) # E: Cannot instantiate abstract class +np.character("test") # E: Cannot instantiate abstract class +np.flexible(b"test") # E: Cannot instantiate abstract class + +np.float64(value=0.0) # E: Unexpected keyword argument +np.int64(value=0) # E: Unexpected keyword argument +np.uint64(value=0) # E: Unexpected keyword argument +np.complex128(value=0.0j) # E: Unexpected keyword argument +np.str_(value='bob') # E: No overload variant +np.bytes_(value=b'test') # E: No overload variant +np.void(value=b'test') # E: Unexpected keyword argument +np.bool_(value=True) # E: Unexpected keyword argument +np.datetime64(value="2019") # E: No overload variant +np.timedelta64(value=0) # E: Unexpected keyword argument + +np.bytes_(b"hello", encoding='utf-8') # E: No overload variant +np.str_("hello", encoding='utf-8') # E: No overload variant diff --git a/numpy/tests/typing/fail/simple.py b/numpy/tests/typing/fail/simple.py new file mode 100644 index 000000000..b5e9d1b13 --- /dev/null +++ b/numpy/tests/typing/fail/simple.py @@ -0,0 +1,10 @@ +"""Simple expression that should fail with mypy.""" + +import numpy as np + +# Array creation routines checks +np.zeros("test") # E: incompatible type +np.zeros() # E: Too few arguments + +np.ones("test") # E: incompatible type +np.ones() # E: Too few arguments diff --git a/numpy/tests/typing/fail/ufuncs.py b/numpy/tests/typing/fail/ufuncs.py new file mode 100644 index 000000000..4da9d08ba --- /dev/null +++ b/numpy/tests/typing/fail/ufuncs.py @@ -0,0 +1,7 @@ +import numpy as np + +np.sin.nin + "foo" # E: Unsupported operand types +np.sin(1, foo="bar") # E: Unexpected keyword argument +np.sin(1, extobj=["foo", "foo", "foo"]) # E: incompatible type + +np.abs(None) # E: incompatible type diff --git a/numpy/tests/typing/fail/warnings_and_errors.py b/numpy/tests/typing/fail/warnings_and_errors.py new file mode 100644 index 000000000..7390cc45f --- /dev/null +++ b/numpy/tests/typing/fail/warnings_and_errors.py @@ -0,0 +1,7 @@ +import numpy as np + +np.AxisError(1.0) # E: Argument 1 to "AxisError" has incompatible type +np.AxisError(1, ndim=2.0) # E: Argument "ndim" to "AxisError" has incompatible type +np.AxisError( + 2, msg_prefix=404 # E: Argument "msg_prefix" to "AxisError" has incompatible type +) diff --git a/numpy/tests/typing/mypy.ini b/numpy/tests/typing/mypy.ini new file mode 100644 index 000000000..91d93588a --- /dev/null +++ b/numpy/tests/typing/mypy.ini @@ -0,0 +1,8 @@ +[mypy] +mypy_path = ../../.. + +[mypy-numpy] +ignore_errors = True + +[mypy-numpy.*] +ignore_errors = True diff --git a/numpy/tests/typing/pass/array_like.py b/numpy/tests/typing/pass/array_like.py new file mode 100644 index 000000000..6b823ca7e --- /dev/null +++ b/numpy/tests/typing/pass/array_like.py @@ -0,0 +1,39 @@ +from typing import Any, List, Optional + +import numpy as np +from numpy.typing import ArrayLike, DtypeLike, _SupportsArray + +x1: ArrayLike = True +x2: ArrayLike = 5 +x3: ArrayLike = 1.0 +x4: ArrayLike = 1 + 1j +x5: ArrayLike = np.int8(1) +x6: ArrayLike = np.float64(1) +x7: ArrayLike = np.complex128(1) +x8: ArrayLike = np.array([1, 2, 3]) +x9: ArrayLike = [1, 2, 3] +x10: ArrayLike = (1, 2, 3) +x11: ArrayLike = "foo" +x12: ArrayLike = memoryview(b'foo') + + +class A: + def __array__(self, dtype: DtypeLike = None) -> np.ndarray: + return np.array([1, 2, 3]) + + +x13: ArrayLike = A() + +scalar: _SupportsArray = np.int64(1) +scalar.__array__(np.float64) +array: _SupportsArray = np.array(1) +array.__array__(np.float64) + +a: _SupportsArray = A() +a.__array__(np.int64) +a.__array__(dtype=np.int64) + +# Escape hatch for when you mean to make something like an object +# array. +object_array_scalar: Any = (i for i in range(10)) +np.array(object_array_scalar) diff --git a/numpy/tests/typing/pass/dtype.py b/numpy/tests/typing/pass/dtype.py new file mode 100644 index 000000000..f954fdd44 --- /dev/null +++ b/numpy/tests/typing/pass/dtype.py @@ -0,0 +1,3 @@ +import numpy as np + +np.dtype(dtype=np.int64) diff --git a/numpy/tests/typing/pass/fromnumeric.py b/numpy/tests/typing/pass/fromnumeric.py new file mode 100644 index 000000000..d9dd45c54 --- /dev/null +++ b/numpy/tests/typing/pass/fromnumeric.py @@ -0,0 +1,185 @@ +"""Tests for :mod:`numpy.core.fromnumeric`.""" + +import numpy as np + +A = np.array(True, ndmin=2, dtype=bool) +B = np.array(1.0, ndmin=2, dtype=np.float32) +A.setflags(write=False) +B.setflags(write=False) + +a = np.bool_(True) +b = np.float32(1.0) +c = 1.0 + +np.take(a, 0) +np.take(b, 0) +np.take(c, 0) +np.take(A, 0) +np.take(B, 0) +np.take(A, [0]) +np.take(B, [0]) + +np.reshape(a, 1) +np.reshape(b, 1) +np.reshape(c, 1) +np.reshape(A, 1) +np.reshape(B, 1) + +np.choose(a, [True, True]) +np.choose(A, [1.0, 1.0]) + +np.repeat(a, 1) +np.repeat(b, 1) +np.repeat(c, 1) +np.repeat(A, 1) +np.repeat(B, 1) + +np.swapaxes(A, 0, 0) +np.swapaxes(B, 0, 0) + +np.transpose(a) +np.transpose(b) +np.transpose(c) +np.transpose(A) +np.transpose(B) + +np.partition(a, 0, axis=None) +np.partition(b, 0, axis=None) +np.partition(c, 0, axis=None) +np.partition(A, 0) +np.partition(B, 0) + +np.argpartition(a, 0) +np.argpartition(b, 0) +np.argpartition(c, 0) +np.argpartition(A, 0) +np.argpartition(B, 0) + +np.sort(A, 0) +np.sort(B, 0) + +np.argsort(A, 0) +np.argsort(B, 0) + +np.argmax(A) +np.argmax(B) +np.argmax(A, axis=0) +np.argmax(B, axis=0) + +np.argmin(A) +np.argmin(B) +np.argmin(A, axis=0) +np.argmin(B, axis=0) + +np.searchsorted(A[0], 0) +np.searchsorted(B[0], 0) +np.searchsorted(A[0], [0]) +np.searchsorted(B[0], [0]) + +np.resize(a, (5, 5)) +np.resize(b, (5, 5)) +np.resize(c, (5, 5)) +np.resize(A, (5, 5)) +np.resize(B, (5, 5)) + +np.squeeze(a) +np.squeeze(b) +np.squeeze(c) +np.squeeze(A) +np.squeeze(B) + +np.diagonal(A) +np.diagonal(B) + +np.trace(A) +np.trace(B) + +np.ravel(a) +np.ravel(b) +np.ravel(c) +np.ravel(A) +np.ravel(B) + +np.nonzero(A) +np.nonzero(B) + +np.shape(a) +np.shape(b) +np.shape(c) +np.shape(A) +np.shape(B) + +np.compress([True], a) +np.compress([True], b) +np.compress([True], c) +np.compress([True], A) +np.compress([True], B) + +np.clip(a, 0, 1.0) +np.clip(b, -1, 1) +np.clip(a, 0, None) +np.clip(b, None, 1) +np.clip(c, 0, 1) +np.clip(A, 0, 1) +np.clip(B, 0, 1) +np.clip(B, [0, 1], [1, 2]) + +np.sum(a) +np.sum(b) +np.sum(c) +np.sum(A) +np.sum(B) +np.sum(A, axis=0) +np.sum(B, axis=0) + +np.all(a) +np.all(b) +np.all(c) +np.all(A) +np.all(B) +np.all(A, axis=0) +np.all(B, axis=0) +np.all(A, keepdims=True) +np.all(B, keepdims=True) + +np.any(a) +np.any(b) +np.any(c) +np.any(A) +np.any(B) +np.any(A, axis=0) +np.any(B, axis=0) +np.any(A, keepdims=True) +np.any(B, keepdims=True) + +np.cumsum(a) +np.cumsum(b) +np.cumsum(c) +np.cumsum(A) +np.cumsum(B) + +np.ptp(b) +np.ptp(c) +np.ptp(B) +np.ptp(B, axis=0) +np.ptp(B, keepdims=True) + +np.amax(a) +np.amax(b) +np.amax(c) +np.amax(A) +np.amax(B) +np.amax(A, axis=0) +np.amax(B, axis=0) +np.amax(A, keepdims=True) +np.amax(B, keepdims=True) + +np.amin(a) +np.amin(b) +np.amin(c) +np.amin(A) +np.amin(B) +np.amin(A, axis=0) +np.amin(B, axis=0) +np.amin(A, keepdims=True) +np.amin(B, keepdims=True) diff --git a/numpy/tests/typing/pass/ndarray_conversion.py b/numpy/tests/typing/pass/ndarray_conversion.py new file mode 100644 index 000000000..303cf53e4 --- /dev/null +++ b/numpy/tests/typing/pass/ndarray_conversion.py @@ -0,0 +1,94 @@ +import os +import tempfile + +import numpy as np + +nd = np.array([[1, 2], [3, 4]]) +scalar_array = np.array(1) + +# item +scalar_array.item() +nd.item(1) +nd.item(0, 1) +nd.item((0, 1)) + +# tolist is pretty simple + +# itemset +scalar_array.itemset(3) +nd.itemset(3, 0) +nd.itemset((0, 0), 3) + +# tobytes +nd.tobytes() +nd.tobytes("C") +nd.tobytes(None) + +# tofile +if os.name != "nt": + with tempfile.NamedTemporaryFile(suffix=".txt") as tmp: + nd.tofile(tmp.name) + nd.tofile(tmp.name, "") + nd.tofile(tmp.name, sep="") + + nd.tofile(tmp.name, "", "%s") + nd.tofile(tmp.name, format="%s") + + nd.tofile(tmp) + +# dump is pretty simple +# dumps is pretty simple + +# astype +nd.astype("float") +nd.astype(float) + +nd.astype(float, "K") +nd.astype(float, order="K") + +nd.astype(float, "K", "unsafe") +nd.astype(float, casting="unsafe") + +nd.astype(float, "K", "unsafe", True) +nd.astype(float, subok=True) + +nd.astype(float, "K", "unsafe", True, True) +nd.astype(float, copy=True) + +# byteswap +nd.byteswap() +nd.byteswap(True) + +# copy +nd.copy() +nd.copy("C") + +# view +nd.view() +nd.view(np.int64) +nd.view(dtype=np.int64) +nd.view(np.int64, np.matrix) +nd.view(type=np.matrix) + +# getfield +complex_array = np.array([[1 + 1j, 0], [0, 1 - 1j]], dtype=np.complex128) + +complex_array.getfield("float") +complex_array.getfield(float) + +complex_array.getfield("float", 8) +complex_array.getfield(float, offset=8) + +# setflags +nd.setflags() + +nd.setflags(True) +nd.setflags(write=True) + +nd.setflags(True, True) +nd.setflags(write=True, align=True) + +nd.setflags(True, True, False) +nd.setflags(write=True, align=True, uic=False) + +# fill is pretty simple diff --git a/numpy/tests/typing/pass/ndarray_shape_manipulation.py b/numpy/tests/typing/pass/ndarray_shape_manipulation.py new file mode 100644 index 000000000..0ca3dff39 --- /dev/null +++ b/numpy/tests/typing/pass/ndarray_shape_manipulation.py @@ -0,0 +1,47 @@ +import numpy as np + +nd1 = np.array([[1, 2], [3, 4]]) + +# reshape +nd1.reshape(4) +nd1.reshape(2, 2) +nd1.reshape((2, 2)) + +nd1.reshape((2, 2), order="C") +nd1.reshape(4, order="C") + +# resize +nd1.resize() +nd1.resize(4) +nd1.resize(2, 2) +nd1.resize((2, 2)) + +nd1.resize((2, 2), refcheck=True) +nd1.resize(4, refcheck=True) + +nd2 = np.array([[1, 2], [3, 4]]) + +# transpose +nd2.transpose() +nd2.transpose(1, 0) +nd2.transpose((1, 0)) + +# swapaxes +nd2.swapaxes(0, 1) + +# flatten +nd2.flatten() +nd2.flatten("C") + +# ravel +nd2.ravel() +nd2.ravel("C") + +# squeeze +nd2.squeeze() + +nd3 = np.array([[1, 2]]) +nd3.squeeze(0) + +nd4 = np.array([[[1, 2]]]) +nd4.squeeze((0, 1)) diff --git a/numpy/tests/typing/pass/numerictypes.py b/numpy/tests/typing/pass/numerictypes.py new file mode 100644 index 000000000..4f205cabc --- /dev/null +++ b/numpy/tests/typing/pass/numerictypes.py @@ -0,0 +1,29 @@ +import numpy as np + +np.maximum_sctype("S8") +np.maximum_sctype(object) + +np.issctype(object) +np.issctype("S8") + +np.obj2sctype(list) +np.obj2sctype(list, default=None) +np.obj2sctype(list, default=np.string_) + +np.issubclass_(np.int32, int) +np.issubclass_(np.float64, float) +np.issubclass_(np.float64, (int, float)) + +np.issubsctype("int64", int) +np.issubsctype(np.array([1]), np.array([1])) + +np.issubdtype("S1", np.string_) +np.issubdtype(np.float64, np.float32) + +np.sctype2char("S1") +np.sctype2char(list) + +np.find_common_type([], [np.int64, np.float32, complex]) +np.find_common_type((), (np.int64, np.float32, complex)) +np.find_common_type([np.int64, np.float32], []) +np.find_common_type([np.float32], [np.int64, np.float64]) diff --git a/numpy/tests/typing/pass/scalars.py b/numpy/tests/typing/pass/scalars.py new file mode 100644 index 000000000..1c7ace282 --- /dev/null +++ b/numpy/tests/typing/pass/scalars.py @@ -0,0 +1,98 @@ +import numpy as np + + +# Construction +class C: + def __complex__(self): + return 3j + + +class B: + def __int__(self): + return 4 + + +class A: + def __float__(self): + return 4.0 + + +np.complex64(3j) +np.complex64(C()) +np.complex128(3j) +np.complex128(C()) +np.complex128(None) + +np.int8(4) +np.int16(3.4) +np.int32(4) +np.int64(-1) +np.uint8(B()) +np.uint32() + +np.float16(A()) +np.float32(16) +np.float64(3.0) +np.float64(None) + +np.bytes_(b"hello") +np.bytes_("hello", 'utf-8') +np.bytes_("hello", encoding='utf-8') +np.str_("hello") +np.str_(b"hello", 'utf-8') +np.str_(b"hello", encoding='utf-8') + +# Protocols +float(np.int8(4)) +int(np.int16(5)) +np.int8(np.float32(6)) + +# TODO(alan): test after https://github.com/python/typeshed/pull/2004 +# complex(np.int32(8)) + +abs(np.int8(4)) + +# Array-ish semantics +np.int8().real +np.int16().imag +np.int32().data +np.int64().flags + +np.uint8().itemsize * 2 +np.uint16().ndim + 1 +np.uint32().strides +np.uint64().shape + +# Time structures +np.datetime64() +np.datetime64(0, "D") +np.datetime64("2019") +np.datetime64("2019", "D") +np.datetime64(None) +np.datetime64(None, "D") + +np.timedelta64() +np.timedelta64(0) +np.timedelta64(0, "D") +np.timedelta64(None) +np.timedelta64(None, "D") + +dt_64 = np.datetime64(0, "D") +td_64 = np.timedelta64(1, "h") + +dt_64 + td_64 +dt_64 - dt_64 +dt_64 - td_64 + +td_64 + td_64 +td_64 - td_64 +td_64 / 1.0 +td_64 / td_64 +td_64 % td_64 + +np.void(1) +np.void(np.int64(1)) +np.void(True) +np.void(np.bool_(True)) +np.void(b"test") +np.void(np.bytes_("test")) diff --git a/numpy/tests/typing/pass/simple.py b/numpy/tests/typing/pass/simple.py new file mode 100644 index 000000000..e0157ab81 --- /dev/null +++ b/numpy/tests/typing/pass/simple.py @@ -0,0 +1,176 @@ +"""Simple expression that should pass with mypy.""" +import operator + +import numpy as np +from typing import Iterable # noqa: F401 + +# Basic checks +array = np.array([1, 2]) + + +def ndarray_func(x): + # type: (np.ndarray) -> np.ndarray + return x + + +ndarray_func(np.array([1, 2])) +array == 1 +array.dtype == float + +# Array creation routines checks +ndarray_func(np.zeros([1, 2])) +ndarray_func(np.ones([1, 2])) +ndarray_func(np.empty([1, 2])) + +ndarray_func(np.zeros_like(array)) +ndarray_func(np.ones_like(array)) +ndarray_func(np.empty_like(array)) + +# Dtype construction +np.dtype(float) +np.dtype(np.float64) +np.dtype(None) +np.dtype("float64") +np.dtype(np.dtype(float)) +np.dtype(("U", 10)) +np.dtype((np.int32, (2, 2))) +# Define the arguments on the previous line to prevent bidirectional +# type inference in mypy from broadening the types. +two_tuples_dtype = [("R", "u1"), ("G", "u1"), ("B", "u1")] +np.dtype(two_tuples_dtype) + +three_tuples_dtype = [("R", "u1", 2)] +np.dtype(three_tuples_dtype) + +mixed_tuples_dtype = [("R", "u1"), ("G", np.unicode_, 1)] +np.dtype(mixed_tuples_dtype) + +shape_tuple_dtype = [("R", "u1", (2, 2))] +np.dtype(shape_tuple_dtype) + +shape_like_dtype = [("R", "u1", (2, 2)), ("G", np.unicode_, 1)] +np.dtype(shape_like_dtype) + +object_dtype = [("field1", object)] +np.dtype(object_dtype) + +np.dtype({"col1": ("U10", 0), "col2": ("float32", 10)}) +np.dtype((np.int32, {"real": (np.int16, 0), "imag": (np.int16, 2)})) +np.dtype((np.int32, (np.int8, 4))) + +# Dtype comparision +np.dtype(float) == float +np.dtype(float) != np.float64 +np.dtype(float) < None +np.dtype(float) <= "float64" +np.dtype(float) > np.dtype(float) +np.dtype(float) >= np.dtype(("U", 10)) + +# Iteration and indexing +def iterable_func(x): + # type: (Iterable) -> Iterable + return x + + +iterable_func(array) +[element for element in array] +iter(array) +zip(array, array) +array[1] +array[:] +array[...] +array[:] = 0 + +array_2d = np.ones((3, 3)) +array_2d[:2, :2] +array_2d[..., 0] +array_2d[:2, :2] = 0 + +# Other special methods +len(array) +str(array) +array_scalar = np.array(1) +int(array_scalar) +float(array_scalar) +# currently does not work due to https://github.com/python/typeshed/issues/1904 +# complex(array_scalar) +bytes(array_scalar) +operator.index(array_scalar) +bool(array_scalar) + +# comparisons +array < 1 +array <= 1 +array == 1 +array != 1 +array > 1 +array >= 1 +1 < array +1 <= array +1 == array +1 != array +1 > array +1 >= array + +# binary arithmetic +array + 1 +1 + array +array += 1 + +array - 1 +1 - array +array -= 1 + +array * 1 +1 * array +array *= 1 + +nonzero_array = np.array([1, 2]) +array / 1 +1 / nonzero_array +float_array = np.array([1.0, 2.0]) +float_array /= 1 + +array // 1 +1 // nonzero_array +array //= 1 + +array % 1 +1 % nonzero_array +array %= 1 + +divmod(array, 1) +divmod(1, nonzero_array) + +array ** 1 +1 ** array +array **= 1 + +array << 1 +1 << array +array <<= 1 + +array >> 1 +1 >> array +array >>= 1 + +array & 1 +1 & array +array &= 1 + +array ^ 1 +1 ^ array +array ^= 1 + +array | 1 +1 | array +array |= 1 + +# unary arithmetic +-array ++array +abs(array) +~array + +# Other methods +np.array([1, 2]).transpose() diff --git a/numpy/tests/typing/pass/simple_py3.py b/numpy/tests/typing/pass/simple_py3.py new file mode 100644 index 000000000..c05a1ce61 --- /dev/null +++ b/numpy/tests/typing/pass/simple_py3.py @@ -0,0 +1,6 @@ +import numpy as np + +array = np.array([1, 2]) + +# The @ operator is not in python 2 +array @ array diff --git a/numpy/tests/typing/pass/ufuncs.py b/numpy/tests/typing/pass/ufuncs.py new file mode 100644 index 000000000..82172952a --- /dev/null +++ b/numpy/tests/typing/pass/ufuncs.py @@ -0,0 +1,13 @@ +import numpy as np + +np.sin(1) +np.sin([1, 2, 3]) +np.sin(1, out=np.empty(1)) +np.matmul(np.ones((2, 2, 2)), np.ones((2, 2, 2)), axes=[(0, 1), (0, 1), (0, 1)]) +np.sin(1, signature="D") +np.sin(1, extobj=[16, 1, lambda: None]) +np.sin(1) + np.sin(1) +np.sin.types[0] +np.sin.__name__ + +np.abs(np.array([1])) diff --git a/numpy/tests/typing/pass/warnings_and_errors.py b/numpy/tests/typing/pass/warnings_and_errors.py new file mode 100644 index 000000000..5b6ec2626 --- /dev/null +++ b/numpy/tests/typing/pass/warnings_and_errors.py @@ -0,0 +1,7 @@ +import numpy as np + +np.AxisError(1) +np.AxisError(1, ndim=2) +np.AxisError(1, ndim=None) +np.AxisError(1, ndim=2, msg_prefix="error") +np.AxisError(1, ndim=2, msg_prefix=None) diff --git a/numpy/tests/typing/reveal/constants.py b/numpy/tests/typing/reveal/constants.py new file mode 100644 index 000000000..8e00810bd --- /dev/null +++ b/numpy/tests/typing/reveal/constants.py @@ -0,0 +1,44 @@ +import numpy as np + +reveal_type(np.Inf) # E: float +reveal_type(np.Infinity) # E: float +reveal_type(np.NAN) # E: float +reveal_type(np.NINF) # E: float +reveal_type(np.NZERO) # E: float +reveal_type(np.NaN) # E: float +reveal_type(np.PINF) # E: float +reveal_type(np.PZERO) # E: float +reveal_type(np.e) # E: float +reveal_type(np.euler_gamma) # E: float +reveal_type(np.inf) # E: float +reveal_type(np.infty) # E: float +reveal_type(np.nan) # E: float +reveal_type(np.pi) # E: float + +reveal_type(np.ALLOW_THREADS) # E: int +reveal_type(np.BUFSIZE) # E: int +reveal_type(np.CLIP) # E: int +reveal_type(np.ERR_CALL) # E: int +reveal_type(np.ERR_DEFAULT) # E: int +reveal_type(np.ERR_IGNORE) # E: int +reveal_type(np.ERR_LOG) # E: int +reveal_type(np.ERR_PRINT) # E: int +reveal_type(np.ERR_RAISE) # E: int +reveal_type(np.ERR_WARN) # E: int +reveal_type(np.FLOATING_POINT_SUPPORT) # E: int +reveal_type(np.FPE_DIVIDEBYZERO) # E: int +reveal_type(np.FPE_INVALID) # E: int +reveal_type(np.FPE_OVERFLOW) # E: int +reveal_type(np.FPE_UNDERFLOW) # E: int +reveal_type(np.MAXDIMS) # E: int +reveal_type(np.MAY_SHARE_BOUNDS) # E: int +reveal_type(np.MAY_SHARE_EXACT) # E: int +reveal_type(np.RAISE) # E: int +reveal_type(np.SHIFT_DIVIDEBYZERO) # E: int +reveal_type(np.SHIFT_INVALID) # E: int +reveal_type(np.SHIFT_OVERFLOW) # E: int +reveal_type(np.SHIFT_UNDERFLOW) # E: int +reveal_type(np.UFUNC_BUFSIZE_DEFAULT) # E: int +reveal_type(np.WRAP) # E: int +reveal_type(np.little_endian) # E: int +reveal_type(np.tracemalloc_domain) # E: int diff --git a/numpy/tests/typing/reveal/fromnumeric.py b/numpy/tests/typing/reveal/fromnumeric.py new file mode 100644 index 000000000..f5feb3f5f --- /dev/null +++ b/numpy/tests/typing/reveal/fromnumeric.py @@ -0,0 +1,205 @@ +"""Tests for :mod:`numpy.core.fromnumeric`.""" + +import numpy as np + +A = np.array(True, ndmin=2, dtype=bool) +B = np.array(1.0, ndmin=2, dtype=np.float32) +A.setflags(write=False) +B.setflags(write=False) + +a = np.bool_(True) +b = np.float32(1.0) +c = 1.0 + +reveal_type(np.take(a, 0)) # E: numpy.bool_ +reveal_type(np.take(b, 0)) # E: numpy.float32 +reveal_type( + np.take(c, 0) # E: Union[numpy.generic, datetime.datetime, datetime.timedelta] +) +reveal_type( + np.take(A, 0) # E: Union[numpy.generic, datetime.datetime, datetime.timedelta] +) +reveal_type( + np.take(B, 0) # E: Union[numpy.generic, datetime.datetime, datetime.timedelta] +) +reveal_type( + np.take( # E: Union[Union[numpy.generic, datetime.datetime, datetime.timedelta], numpy.ndarray] + A, [0] + ) +) +reveal_type( + np.take( # E: Union[Union[numpy.generic, datetime.datetime, datetime.timedelta], numpy.ndarray] + B, [0] + ) +) + +reveal_type(np.reshape(a, 1)) # E: numpy.ndarray +reveal_type(np.reshape(b, 1)) # E: numpy.ndarray +reveal_type(np.reshape(c, 1)) # E: numpy.ndarray +reveal_type(np.reshape(A, 1)) # E: numpy.ndarray +reveal_type(np.reshape(B, 1)) # E: numpy.ndarray + +reveal_type(np.choose(a, [True, True])) # E: numpy.bool_ +reveal_type(np.choose(A, [True, True])) # E: numpy.ndarray + +reveal_type(np.repeat(a, 1)) # E: numpy.ndarray +reveal_type(np.repeat(b, 1)) # E: numpy.ndarray +reveal_type(np.repeat(c, 1)) # E: numpy.ndarray +reveal_type(np.repeat(A, 1)) # E: numpy.ndarray +reveal_type(np.repeat(B, 1)) # E: numpy.ndarray + +# TODO: Add tests for np.put() + +reveal_type(np.swapaxes(A, 0, 0)) # E: numpy.ndarray +reveal_type(np.swapaxes(B, 0, 0)) # E: numpy.ndarray + +reveal_type(np.transpose(a)) # E: numpy.ndarray +reveal_type(np.transpose(b)) # E: numpy.ndarray +reveal_type(np.transpose(c)) # E: numpy.ndarray +reveal_type(np.transpose(A)) # E: numpy.ndarray +reveal_type(np.transpose(B)) # E: numpy.ndarray + +reveal_type(np.partition(a, 0, axis=None)) # E: numpy.ndarray +reveal_type(np.partition(b, 0, axis=None)) # E: numpy.ndarray +reveal_type(np.partition(c, 0, axis=None)) # E: numpy.ndarray +reveal_type(np.partition(A, 0)) # E: numpy.ndarray +reveal_type(np.partition(B, 0)) # E: numpy.ndarray + +reveal_type(np.argpartition(a, 0)) # E: numpy.integer +reveal_type(np.argpartition(b, 0)) # E: numpy.integer +reveal_type(np.argpartition(c, 0)) # E: numpy.ndarray +reveal_type(np.argpartition(A, 0)) # E: numpy.ndarray +reveal_type(np.argpartition(B, 0)) # E: numpy.ndarray + +reveal_type(np.sort(A, 0)) # E: numpy.ndarray +reveal_type(np.sort(B, 0)) # E: numpy.ndarray + +reveal_type(np.argsort(A, 0)) # E: numpy.ndarray +reveal_type(np.argsort(B, 0)) # E: numpy.ndarray + +reveal_type(np.argmax(A)) # E: numpy.integer +reveal_type(np.argmax(B)) # E: numpy.integer +reveal_type(np.argmax(A, axis=0)) # E: Union[numpy.integer, numpy.ndarray] +reveal_type(np.argmax(B, axis=0)) # E: Union[numpy.integer, numpy.ndarray] + +reveal_type(np.argmin(A)) # E: numpy.integer +reveal_type(np.argmin(B)) # E: numpy.integer +reveal_type(np.argmin(A, axis=0)) # E: Union[numpy.integer, numpy.ndarray] +reveal_type(np.argmin(B, axis=0)) # E: Union[numpy.integer, numpy.ndarray] + +reveal_type(np.searchsorted(A[0], 0)) # E: numpy.integer +reveal_type(np.searchsorted(B[0], 0)) # E: numpy.integer +reveal_type(np.searchsorted(A[0], [0])) # E: numpy.ndarray +reveal_type(np.searchsorted(B[0], [0])) # E: numpy.ndarray + +reveal_type(np.resize(a, (5, 5))) # E: numpy.ndarray +reveal_type(np.resize(b, (5, 5))) # E: numpy.ndarray +reveal_type(np.resize(c, (5, 5))) # E: numpy.ndarray +reveal_type(np.resize(A, (5, 5))) # E: numpy.ndarray +reveal_type(np.resize(B, (5, 5))) # E: numpy.ndarray + +reveal_type(np.squeeze(a)) # E: numpy.bool_ +reveal_type(np.squeeze(b)) # E: numpy.float32 +reveal_type(np.squeeze(c)) # E: numpy.ndarray +reveal_type(np.squeeze(A)) # E: numpy.ndarray +reveal_type(np.squeeze(B)) # E: numpy.ndarray + +reveal_type(np.diagonal(A)) # E: numpy.ndarray +reveal_type(np.diagonal(B)) # E: numpy.ndarray + +reveal_type(np.trace(A)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.trace(B)) # E: Union[numpy.number, numpy.ndarray] + +reveal_type(np.ravel(a)) # E: numpy.ndarray +reveal_type(np.ravel(b)) # E: numpy.ndarray +reveal_type(np.ravel(c)) # E: numpy.ndarray +reveal_type(np.ravel(A)) # E: numpy.ndarray +reveal_type(np.ravel(B)) # E: numpy.ndarray + +reveal_type(np.nonzero(a)) # E: tuple[numpy.ndarray] +reveal_type(np.nonzero(b)) # E: tuple[numpy.ndarray] +reveal_type(np.nonzero(c)) # E: tuple[numpy.ndarray] +reveal_type(np.nonzero(A)) # E: tuple[numpy.ndarray] +reveal_type(np.nonzero(B)) # E: tuple[numpy.ndarray] + +reveal_type(np.shape(a)) # E: tuple[builtins.int] +reveal_type(np.shape(b)) # E: tuple[builtins.int] +reveal_type(np.shape(c)) # E: tuple[builtins.int] +reveal_type(np.shape(A)) # E: tuple[builtins.int] +reveal_type(np.shape(B)) # E: tuple[builtins.int] + +reveal_type(np.compress([True], a)) # E: numpy.ndarray +reveal_type(np.compress([True], b)) # E: numpy.ndarray +reveal_type(np.compress([True], c)) # E: numpy.ndarray +reveal_type(np.compress([True], A)) # E: numpy.ndarray +reveal_type(np.compress([True], B)) # E: numpy.ndarray + +reveal_type(np.clip(a, 0, 1.0)) # E: numpy.number +reveal_type(np.clip(b, -1, 1)) # E: numpy.float32 +reveal_type(np.clip(c, 0, 1)) # E: numpy.number +reveal_type(np.clip(A, 0, 1)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.clip(B, 0, 1)) # E: Union[numpy.number, numpy.ndarray] + +reveal_type(np.sum(a)) # E: numpy.number +reveal_type(np.sum(b)) # E: numpy.float32 +reveal_type(np.sum(c)) # E: numpy.number +reveal_type(np.sum(A)) # E: numpy.number +reveal_type(np.sum(B)) # E: numpy.number +reveal_type(np.sum(A, axis=0)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.sum(B, axis=0)) # E: Union[numpy.number, numpy.ndarray] + +reveal_type(np.all(a)) # E: numpy.bool_ +reveal_type(np.all(b)) # E: numpy.bool_ +reveal_type(np.all(c)) # E: numpy.bool_ +reveal_type(np.all(A)) # E: numpy.bool_ +reveal_type(np.all(B)) # E: numpy.bool_ +reveal_type(np.all(A, axis=0)) # E: Union[numpy.bool_, numpy.ndarray] +reveal_type(np.all(B, axis=0)) # E: Union[numpy.bool_, numpy.ndarray] +reveal_type(np.all(A, keepdims=True)) # E: Union[numpy.bool_, numpy.ndarray] +reveal_type(np.all(B, keepdims=True)) # E: Union[numpy.bool_, numpy.ndarray] + +reveal_type(np.any(a)) # E: numpy.bool_ +reveal_type(np.any(b)) # E: numpy.bool_ +reveal_type(np.any(c)) # E: numpy.bool_ +reveal_type(np.any(A)) # E: numpy.bool_ +reveal_type(np.any(B)) # E: numpy.bool_ +reveal_type(np.any(A, axis=0)) # E: Union[numpy.bool_, numpy.ndarray] +reveal_type(np.any(B, axis=0)) # E: Union[numpy.bool_, numpy.ndarray] +reveal_type(np.any(A, keepdims=True)) # E: Union[numpy.bool_, numpy.ndarray] +reveal_type(np.any(B, keepdims=True)) # E: Union[numpy.bool_, numpy.ndarray] + +reveal_type(np.cumsum(a)) # E: numpy.ndarray +reveal_type(np.cumsum(b)) # E: numpy.ndarray +reveal_type(np.cumsum(c)) # E: numpy.ndarray +reveal_type(np.cumsum(A)) # E: numpy.ndarray +reveal_type(np.cumsum(B)) # E: numpy.ndarray + +reveal_type(np.ptp(a)) # E: numpy.number +reveal_type(np.ptp(b)) # E: numpy.float32 +reveal_type(np.ptp(c)) # E: numpy.number +reveal_type(np.ptp(A)) # E: numpy.number +reveal_type(np.ptp(B)) # E: numpy.number +reveal_type(np.ptp(A, axis=0)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.ptp(B, axis=0)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.ptp(A, keepdims=True)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.ptp(B, keepdims=True)) # E: Union[numpy.number, numpy.ndarray] + +reveal_type(np.amax(a)) # E: numpy.number +reveal_type(np.amax(b)) # E: numpy.float32 +reveal_type(np.amax(c)) # E: numpy.number +reveal_type(np.amax(A)) # E: numpy.number +reveal_type(np.amax(B)) # E: numpy.number +reveal_type(np.amax(A, axis=0)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.amax(B, axis=0)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.amax(A, keepdims=True)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.amax(B, keepdims=True)) # E: Union[numpy.number, numpy.ndarray] + +reveal_type(np.amin(a)) # E: numpy.number +reveal_type(np.amin(b)) # E: numpy.float32 +reveal_type(np.amin(c)) # E: numpy.number +reveal_type(np.amin(A)) # E: numpy.number +reveal_type(np.amin(B)) # E: numpy.number +reveal_type(np.amin(A, axis=0)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.amin(B, axis=0)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.amin(A, keepdims=True)) # E: Union[numpy.number, numpy.ndarray] +reveal_type(np.amin(B, keepdims=True)) # E: Union[numpy.number, numpy.ndarray] diff --git a/numpy/tests/typing/reveal/ndarray_conversion.py b/numpy/tests/typing/reveal/ndarray_conversion.py new file mode 100644 index 000000000..411adcf63 --- /dev/null +++ b/numpy/tests/typing/reveal/ndarray_conversion.py @@ -0,0 +1,54 @@ +import numpy as np + +nd = np.array([[1, 2], [3, 4]]) + +# item +reveal_type(nd.item()) # E: Any +reveal_type(nd.item(1)) # E: Any +reveal_type(nd.item(0, 1)) # E: Any +reveal_type(nd.item((0, 1))) # E: Any + +# tolist +reveal_type(nd.tolist()) # E: builtins.list[Any] + +# itemset does not return a value +# tostring is pretty simple +# tobytes is pretty simple +# tofile does not return a value +# dump does not return a value +# dumps is pretty simple + +# astype +reveal_type(nd.astype("float")) # E: numpy.ndarray +reveal_type(nd.astype(float)) # E: numpy.ndarray +reveal_type(nd.astype(float, "K")) # E: numpy.ndarray +reveal_type(nd.astype(float, "K", "unsafe")) # E: numpy.ndarray +reveal_type(nd.astype(float, "K", "unsafe", True)) # E: numpy.ndarray +reveal_type(nd.astype(float, "K", "unsafe", True, True)) # E: numpy.ndarray + +# byteswap +reveal_type(nd.byteswap()) # E: numpy.ndarray +reveal_type(nd.byteswap(True)) # E: numpy.ndarray + +# copy +reveal_type(nd.copy()) # E: numpy.ndarray +reveal_type(nd.copy("C")) # E: numpy.ndarray + +# view +class SubArray(np.ndarray): + pass + + +reveal_type(nd.view()) # E: numpy.ndarray +reveal_type(nd.view(np.int64)) # E: numpy.ndarray +# replace `Any` with `numpy.matrix` when `matrix` will be added to stubs +reveal_type(nd.view(np.int64, np.matrix)) # E: Any +reveal_type(nd.view(np.int64, SubArray)) # E: SubArray + +# getfield +reveal_type(nd.getfield("float")) # E: numpy.ndarray +reveal_type(nd.getfield(float)) # E: numpy.ndarray +reveal_type(nd.getfield(float, 8)) # E: numpy.ndarray + +# setflags does not return a value +# fill does not return a value diff --git a/numpy/tests/typing/reveal/ndarray_shape_manipulation.py b/numpy/tests/typing/reveal/ndarray_shape_manipulation.py new file mode 100644 index 000000000..a44e1cfa1 --- /dev/null +++ b/numpy/tests/typing/reveal/ndarray_shape_manipulation.py @@ -0,0 +1,35 @@ +import numpy as np + +nd = np.array([[1, 2], [3, 4]]) + +# reshape +reveal_type(nd.reshape()) # E: numpy.ndarray +reveal_type(nd.reshape(4)) # E: numpy.ndarray +reveal_type(nd.reshape(2, 2)) # E: numpy.ndarray +reveal_type(nd.reshape((2, 2))) # E: numpy.ndarray + +reveal_type(nd.reshape((2, 2), order="C")) # E: numpy.ndarray +reveal_type(nd.reshape(4, order="C")) # E: numpy.ndarray + +# resize does not return a value + +# transpose +reveal_type(nd.transpose()) # E: numpy.ndarray +reveal_type(nd.transpose(1, 0)) # E: numpy.ndarray +reveal_type(nd.transpose((1, 0))) # E: numpy.ndarray + +# swapaxes +reveal_type(nd.swapaxes(0, 1)) # E: numpy.ndarray + +# flatten +reveal_type(nd.flatten()) # E: numpy.ndarray +reveal_type(nd.flatten("C")) # E: numpy.ndarray + +# ravel +reveal_type(nd.ravel()) # E: numpy.ndarray +reveal_type(nd.ravel("C")) # E: numpy.ndarray + +# squeeze +reveal_type(nd.squeeze()) # E: numpy.ndarray +reveal_type(nd.squeeze(0)) # E: numpy.ndarray +reveal_type(nd.squeeze((0, 2))) # E: numpy.ndarray diff --git a/numpy/tests/typing/reveal/numerictypes.py b/numpy/tests/typing/reveal/numerictypes.py new file mode 100644 index 000000000..e026158cd --- /dev/null +++ b/numpy/tests/typing/reveal/numerictypes.py @@ -0,0 +1,18 @@ +import numpy as np + +reveal_type(np.issctype(np.generic)) # E: bool +reveal_type(np.issctype("foo")) # E: bool + +reveal_type(np.obj2sctype("S8")) # E: Union[numpy.generic, None] +reveal_type(np.obj2sctype("S8", default=None)) # E: Union[numpy.generic, None] +reveal_type( + np.obj2sctype("foo", default=int) # E: Union[numpy.generic, Type[builtins.int*]] +) + +reveal_type(np.issubclass_(np.float64, float)) # E: bool +reveal_type(np.issubclass_(np.float64, (int, float))) # E: bool + +reveal_type(np.sctype2char("S8")) # E: str +reveal_type(np.sctype2char(list)) # E: str + +reveal_type(np.find_common_type([np.int64], [np.int64])) # E: numpy.dtype diff --git a/numpy/tests/typing/reveal/scalars.py b/numpy/tests/typing/reveal/scalars.py new file mode 100644 index 000000000..8a9555fc3 --- /dev/null +++ b/numpy/tests/typing/reveal/scalars.py @@ -0,0 +1,30 @@ +import numpy as np + +x = np.complex64(3 + 2j) + +reveal_type(x.real) # E: numpy.float32 +reveal_type(x.imag) # E: numpy.float32 + +reveal_type(x.real.real) # E: numpy.float32 +reveal_type(x.real.imag) # E: numpy.float32 + +reveal_type(x.itemsize) # E: int +reveal_type(x.shape) # E: tuple[builtins.int] +reveal_type(x.strides) # E: tuple[builtins.int] + +# Time structures +dt = np.datetime64(0, "D") +td = np.timedelta64(0, "D") + +reveal_type(dt + td) # E: numpy.datetime64 +reveal_type(dt + 1) # E: numpy.datetime64 +reveal_type(dt - dt) # E: numpy.timedelta64 +reveal_type(dt - 1) # E: numpy.timedelta64 + +reveal_type(td + td) # E: numpy.timedelta64 +reveal_type(td + 1) # E: numpy.timedelta64 +reveal_type(td - td) # E: numpy.timedelta64 +reveal_type(td - 1) # E: numpy.timedelta64 +reveal_type(td / 1.0) # E: numpy.timedelta64 +reveal_type(td / td) # E: float +reveal_type(td % td) # E: numpy.timedelta64 diff --git a/numpy/tests/typing/reveal/warnings_and_errors.py b/numpy/tests/typing/reveal/warnings_and_errors.py new file mode 100644 index 000000000..c428deb7a --- /dev/null +++ b/numpy/tests/typing/reveal/warnings_and_errors.py @@ -0,0 +1,10 @@ +from typing import Type + +import numpy as np + +reveal_type(np.ModuleDeprecationWarning()) # E: numpy.ModuleDeprecationWarning +reveal_type(np.VisibleDeprecationWarning()) # E: numpy.VisibleDeprecationWarning +reveal_type(np.ComplexWarning()) # E: numpy.ComplexWarning +reveal_type(np.RankWarning()) # E: numpy.RankWarning +reveal_type(np.TooHardError()) # E: numpy.TooHardError +reveal_type(np.AxisError(1)) # E: numpy.AxisError diff --git a/numpy/typing/__init__.py b/numpy/typing/__init__.py new file mode 100644 index 000000000..f2000823f --- /dev/null +++ b/numpy/typing/__init__.py @@ -0,0 +1,81 @@ +""" +============================ +Typing (:mod:`numpy.typing`) +============================ + +.. warning:: + + Some of the types in this module rely on features only present in + the standard library in Python 3.8 and greater. If you want to use + these types in earlier versions of Python, you should install the + typing-extensions_ package. + +Large parts of the NumPy API have PEP-484-style type annotations. In +addition, the following type aliases are available for users. + +- ``typing.ArrayLike``: objects that can be converted to arrays +- ``typing.DtypeLike``: objects that can be converted to dtypes + +Roughly speaking, ``typing.ArrayLike`` is "objects that can be used as +inputs to ``np.array``" and ``typing.DtypeLike`` is "objects that can +be used as inputs to ``np.dtype``". + +.. _typing-extensions: https://pypi.org/project/typing-extensions/ + +Differences from the runtime NumPy API +-------------------------------------- + +NumPy is very flexible. Trying to describe the full range of +possibilities statically would result in types that are not very +helpful. For that reason, the typed NumPy API is often stricter than +the runtime NumPy API. This section describes some notable +differences. + +ArrayLike +~~~~~~~~~ + +The ``ArrayLike`` type tries to avoid creating object arrays. For +example, + +.. code-block:: python + + >>> np.array(x**2 for x in range(10)) + array(<generator object <genexpr> at 0x10c004cd0>, dtype=object) + +is valid NumPy code which will create a 0-dimensional object +array. Type checkers will complain about the above example when using +the NumPy types however. If you really intended to do the above, then +you can either use a ``# type: ignore`` comment: + +.. code-block:: python + + >>> np.array(x**2 for x in range(10)) # type: ignore + +or explicitly type the array like object as ``Any``: + +.. code-block:: python + + >>> from typing import Any + >>> array_like: Any = (x**2 for x in range(10)) + >>> np.array(array_like) + array(<generator object <genexpr> at 0x1192741d0>, dtype=object) + +ndarray +~~~~~~~ + +It's possible to mutate the dtype of an array at runtime. For example, +the following code is valid: + +.. code-block:: python + + x = np.array([1, 2]) + x.dtype = np.bool_ + +This sort of mutation is not allowed by the types. Users who want to +write statically typed code should insted use the `numpy.ndarray.view` +method to create a view of the array with a different dtype. + +""" +from ._array_like import _SupportsArray, ArrayLike +from ._shape import _Shape, _ShapeLike +from ._dtype_like import DtypeLike diff --git a/numpy/typing/_array_like.py b/numpy/typing/_array_like.py new file mode 100644 index 000000000..76c0c839c --- /dev/null +++ b/numpy/typing/_array_like.py @@ -0,0 +1,34 @@ +import sys +from typing import Any, overload, Sequence, TYPE_CHECKING, Union + +from numpy import ndarray +from ._dtype_like import DtypeLike + +if sys.version_info >= (3, 8): + from typing import Protocol + HAVE_PROTOCOL = True +else: + try: + from typing_extensions import Protocol + except ImportError: + HAVE_PROTOCOL = False + else: + HAVE_PROTOCOL = True + +if TYPE_CHECKING or HAVE_PROTOCOL: + class _SupportsArray(Protocol): + @overload + def __array__(self, __dtype: DtypeLike = ...) -> ndarray: ... + @overload + def __array__(self, dtype: DtypeLike = ...) -> ndarray: ... +else: + _SupportsArray = Any + +# TODO: support buffer protocols once +# +# https://bugs.python.org/issue27501 +# +# is resolved. See also the mypy issue: +# +# https://github.com/python/typing/issues/593 +ArrayLike = Union[bool, int, float, complex, _SupportsArray, Sequence] diff --git a/numpy/typing/_dtype_like.py b/numpy/typing/_dtype_like.py new file mode 100644 index 000000000..b9df0af04 --- /dev/null +++ b/numpy/typing/_dtype_like.py @@ -0,0 +1,46 @@ +from typing import Any, Dict, List, Sequence, Tuple, Union + +from numpy import dtype +from ._shape import _ShapeLike + +_DtypeLikeNested = Any # TODO: wait for support for recursive types + +# Anything that can be coerced into numpy.dtype. +# Reference: https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html +DtypeLike = Union[ + dtype, + # default data type (float64) + None, + # array-scalar types and generic types + type, # TODO: enumerate these when we add type hints for numpy scalars + # TODO: add a protocol for anything with a dtype attribute + # character codes, type strings or comma-separated fields, e.g., 'float64' + str, + # (flexible_dtype, itemsize) + Tuple[_DtypeLikeNested, int], + # (fixed_dtype, shape) + Tuple[_DtypeLikeNested, _ShapeLike], + # [(field_name, field_dtype, field_shape), ...] + # + # The type here is quite broad because NumPy accepts quite a wide + # range of inputs inside the list; see the tests for some + # examples. + List[Any], + # {'names': ..., 'formats': ..., 'offsets': ..., 'titles': ..., + # 'itemsize': ...} + # TODO: use TypedDict when/if it's officially supported + Dict[ + str, + Union[ + Sequence[str], # names + Sequence[_DtypeLikeNested], # formats + Sequence[int], # offsets + Sequence[Union[bytes, str, None]], # titles + int, # itemsize + ], + ], + # {'field1': ..., 'field2': ..., ...} + Dict[str, Tuple[_DtypeLikeNested, int]], + # (base_dtype, new_dtype) + Tuple[_DtypeLikeNested, _DtypeLikeNested], +] diff --git a/numpy/typing/_shape.py b/numpy/typing/_shape.py new file mode 100644 index 000000000..4629046ea --- /dev/null +++ b/numpy/typing/_shape.py @@ -0,0 +1,6 @@ +from typing import Sequence, Tuple, Union + +_Shape = Tuple[int, ...] + +# Anything that can be coerced to a shape tuple +_ShapeLike = Union[int, Sequence[int]] diff --git a/pyproject.toml b/pyproject.toml index d81b731d3..a54d0b379 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,9 @@ [build-system] # Minimum requirements for the build system to execute. requires = [ - "setuptools", + "setuptools!=49.2.0", "wheel", - "Cython>=0.29.14", # Note: keep in sync with tools/cythonize.py + "Cython>=0.29.21", # Note: keep in sync with tools/cythonize.py ] diff --git a/runtests.py b/runtests.py index 7f1d55b85..beaf668d6 100755 --- a/runtests.py +++ b/runtests.py @@ -114,6 +114,12 @@ def main(argv): help="Number of parallel jobs during build") parser.add_argument("--warn-error", action="store_true", help="Set -Werror to convert all compiler warnings to errors") + parser.add_argument("--cpu-baseline", default=None, + help="Specify a list of enabled baseline CPU optimizations"), + parser.add_argument("--cpu-dispatch", default=None, + help="Specify a list of dispatched CPU optimizations"), + parser.add_argument("--disable-optimization", action="store_true", + help="Disable CPU optimized code(dispatch,simd,fast...)"), parser.add_argument("--show-build-log", action="store_true", help="Show build output rather than using a log file") parser.add_argument("--bench", action="store_true", @@ -388,6 +394,12 @@ def build_project(args): cmd += ["build_src", "--verbose-cfg"] if args.warn_error: cmd += ["--warn-error"] + if args.cpu_baseline: + cmd += ["--cpu-baseline", args.cpu_baseline] + if args.cpu_dispatch: + cmd += ["--cpu-dispatch", args.cpu_dispatch] + if args.disable_optimization: + cmd += ["--disable-optimization"] # Install; avoid producing eggs so numpy can be imported from dst_dir. cmd += ['install', '--prefix=' + dst_dir, '--single-version-externally-managed', @@ -403,6 +403,16 @@ def parse_setuppy_commands(): return True +def get_docs_url(): + if not ISRELEASED: + return "https://numpy.org/devdocs" + else: + # For releaeses, this URL ends up on pypi. + # By pinning the version, users looking at old PyPI releases can get + # to the associated docs easily. + return "https://numpy.org/doc/{}.{}".format(MAJOR, MINOR) + + def setup_package(): src_path = os.path.dirname(os.path.abspath(__file__)) old_path = os.getcwd() @@ -437,7 +447,7 @@ def setup_package(): download_url = "https://pypi.python.org/pypi/numpy", project_urls={ "Bug Tracker": "https://github.com/numpy/numpy/issues", - "Documentation": "https://docs.scipy.org/doc/numpy/", + "Documentation": get_docs_url(), "Source Code": "https://github.com/numpy/numpy", }, license = 'BSD', diff --git a/shippable.yml b/shippable.yml index dc3617e12..2843377e2 100644 --- a/shippable.yml +++ b/shippable.yml @@ -1,7 +1,7 @@ branches: only: - - master - - maintenance/* + - master + - maintenance/* language: python @@ -53,15 +53,7 @@ build: # run the test suite - python runtests.py -n --debug-info --show-build-log -- -rsx --junit-xml=$SHIPPABLE_REPO_DIR/shippable/testresults/tests.xml -n 2 --durations=10 - cache: true - cache_dir_list: - # the NumPy project uses a single Amazon S3 cache - # so upload the parent path of the Python-specific - # version paths to avoid i.e., 3.6 overwriting - # 3.7 pip cache (seems to be an issue) - - /root/.cache/pip/wheels - - + cache: false # disable email notification # of CI job result diff --git a/site.cfg.example b/site.cfg.example index 3eba3bbd7..c809303a2 100644 --- a/site.cfg.example +++ b/site.cfg.example @@ -242,14 +242,6 @@ # library_dirs = C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2018\windows\mkl\lib\intel64 # libraries = mkl_rt -# Accelerate -# ---------- -# Accelerate/vecLib is an OSX framework providing a BLAS and LAPACK implementation. -# -# [accelerate] -# libraries = Accelerate, vecLib -# #libraries = None - # UMFPACK # ------- # The UMFPACK library is used in scikits.umfpack to factor large sparse matrices. diff --git a/test_requirements.txt b/test_requirements.txt index 026bbe414..4bf6cf4b0 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,9 +1,17 @@ -cython==0.29.19 -hypothesis==5.16.0 -pytest==5.4.2 +cython==0.29.21 +wheel +setuptools!=49.2.0 +hypothesis==5.19.1 +pytest==5.4.3 pytz==2020.1 -pytest-cov==2.9.0 +pytest-cov==2.10.0 pickle5; python_version == '3.7' pickle5; python_version == '3.6' and platform_python_implementation != 'PyPy' # for numpy.random.test.test_extending cffi +# For testing types. Notes on the restrictions: +# - Mypy relies on C API features not present in PyPy +# - Mypy doesn't currently work on Python 3.9 +# - Python 3.6 doesn't work because it doesn't understand py.typed +mypy==0.782; platform_python_implementation != "PyPy" and python_version > "3.6" +typing_extensions diff --git a/tools/cythonize.py b/tools/cythonize.py index 65b79f716..6cebf0f72 100755 --- a/tools/cythonize.py +++ b/tools/cythonize.py @@ -66,11 +66,11 @@ def process_pyx(fromfile, tofile): # check the version, and invoke through python from distutils.version import LooseVersion - # Cython 0.29.14 is required for Python 3.8 and there are + # Cython 0.29.21 is required for Python 3.9 and there are # other fixes in the 0.29 series that are needed even for earlier # Python versions. # Note: keep in sync with that in pyproject.toml - required_version = LooseVersion('0.29.14') + required_version = LooseVersion('0.29.21') if LooseVersion(cython_version) < required_version: raise RuntimeError(f'Building {VENDOR} requires Cython >= {required_version}') diff --git a/tools/functions_missing_types.py b/tools/functions_missing_types.py new file mode 100755 index 000000000..0fee97777 --- /dev/null +++ b/tools/functions_missing_types.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +"""Find the functions in a module missing type annotations. + +To use it run + +./functions_missing_types.py <module> + +and it will print out a list of functions in the module that don't +have types. + +""" +import argparse +import ast +import importlib +import os + +NUMPY_ROOT = os.path.dirname(os.path.join( + os.path.abspath(__file__), "..", +)) + +# Technically "public" functions (they don't start with an underscore) +# that we don't want to include. +EXCLUDE_LIST = { + "numpy": { + # Stdlib modules in the namespace by accident + "absolute_import", + "division", + "print_function", + "warnings", + # Accidentally public, deprecated, or shouldn't be used + "Tester", + "alen", + "add_docstring", + "add_newdoc", + "add_newdoc_ufunc", + "core", + "fastCopyAndTranspose", + "get_array_wrap", + "int_asbuffer", + "oldnumeric", + "safe_eval", + "set_numeric_ops", + "test", + # Builtins + "bool", + "complex", + "float", + "int", + "long", + "object", + "str", + "unicode", + # Should use numpy_financial instead + "fv", + "ipmt", + "irr", + "mirr", + "nper", + "npv", + "pmt", + "ppmt", + "pv", + "rate", + # More standard names should be preferred + "alltrue", # all + "sometrue", # any + } +} + + +class FindAttributes(ast.NodeVisitor): + """Find top-level attributes/functions/classes in stubs files. + + Do this by walking the stubs ast. See e.g. + + https://greentreesnakes.readthedocs.io/en/latest/index.html + + for more information on working with Python's ast. + + """ + + def __init__(self): + self.attributes = set() + + def visit_FunctionDef(self, node): + if node.name == "__getattr__": + # Not really a module member. + return + self.attributes.add(node.name) + # Do not call self.generic_visit; we are only interested in + # top-level functions. + return + + def visit_ClassDef(self, node): + if not node.name.startswith("_"): + self.attributes.add(node.name) + return + + def visit_AnnAssign(self, node): + self.attributes.add(node.target.id) + + +def find_missing(module_name): + module_path = os.path.join( + NUMPY_ROOT, + module_name.replace(".", os.sep), + "__init__.pyi", + ) + + module = importlib.import_module(module_name) + module_attributes = { + attribute for attribute in dir(module) if not attribute.startswith("_") + } + + if os.path.isfile(module_path): + with open(module_path) as f: + tree = ast.parse(f.read()) + ast_visitor = FindAttributes() + ast_visitor.visit(tree) + stubs_attributes = ast_visitor.attributes + else: + # No stubs for this module yet. + stubs_attributes = set() + + exclude_list = EXCLUDE_LIST.get(module_name, set()) + + missing = module_attributes - stubs_attributes - exclude_list + print("\n".join(sorted(missing))) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("module") + args = parser.parse_args() + + find_missing(args.module) + + +if __name__ == "__main__": + main() diff --git a/tools/openblas_support.py b/tools/openblas_support.py index cbb6a5e43..9537c3e2f 100644 --- a/tools/openblas_support.py +++ b/tools/openblas_support.py @@ -10,39 +10,88 @@ import zipfile from tempfile import mkstemp, gettempdir from urllib.request import urlopen, Request +from urllib.error import HTTPError -OPENBLAS_V = '0.3.9' +OPENBLAS_V = '0.3.10' # Temporary build of OpenBLAS to test a fix for dynamic detection of CPU -OPENBLAS_LONG = 'v0.3.7-527-g79fd006c' # the 0.3.7 is misleading +OPENBLAS_LONG = 'v0.3.10' BASE_LOC = 'https://anaconda.org/multibuild-wheels-staging/openblas-libs' BASEURL = f'{BASE_LOC}/{OPENBLAS_LONG}/download' -ARCHITECTURES = ['', 'windows', 'darwin', 'aarch64', 'x86_64', 'i686', 'ppc64le', 's390x'] +ARCHITECTURES = ['', 'windows', 'darwin', 'aarch64', 'x86_64', + 'i686', 'ppc64le', 's390x'] sha256_vals = { -"openblas-v0.3.7-527-g79fd006c-win_amd64-gcc_7_1_0.zip": "7249d68c02e6b6339e06edfeab1fecddf29ee1e67a3afaa77917c320c43de840", -"openblas64_-v0.3.7-527-g79fd006c-win_amd64-gcc_7_1_0.zip": "6488e0961a5926e47242f63b63b41cfdd661e6f1d267e8e313e397cde4775c17", -"openblas-v0.3.7-527-g79fd006c-win32-gcc_7_1_0.zip": "5fb0867ca70b1d0fdbf68dd387c0211f26903d74631420e4aabb49e94aa3930d", -"openblas-v0.3.7-527-g79fd006c-macosx_10_9_x86_64-gf_1becaaa.tar.gz": "69434bd626bbc495da9ce8c36b005d140c75e3c47f94e88c764a199e820f9259", -"openblas64_-v0.3.7-527-g79fd006c-macosx_10_9_x86_64-gf_1becaaa.tar.gz": "093f6d953e3fa76a86809be67bd1f0b27656671b5a55b233169cfaa43fd63e22", -"openblas-v0.3.7-527-g79fd006c-manylinux2014_aarch64.tar.gz": "42676c69dc48cd6e412251b39da6b955a5a0e00323ddd77f9137f7c259d35319", -"openblas64_-v0.3.7-527-g79fd006c-manylinux2014_aarch64.tar.gz": "5aec167af4052cf5e9e3e416c522d9794efabf03a2aea78b9bb3adc94f0b73d8", -"openblas-v0.3.7-527-g79fd006c-manylinux2010_x86_64.tar.gz": "fa67c6cc29d4cc5c70a147c80526243239a6f95fc3feadcf83a78176cd9c526b", -"openblas64_-v0.3.7-527-g79fd006c-manylinux2010_x86_64.tar.gz": "9ad34e89a5307dcf5823bf5c020580d0559a0c155fe85b44fc219752e61852b0", -"openblas-v0.3.7-527-g79fd006c-manylinux2010_i686.tar.gz": "0b8595d316c8b7be84ab1f1d5a0c89c1b35f7c987cdaf61d441bcba7ab4c7439", -"openblas-v0.3.7-527-g79fd006c-manylinux2014_ppc64le.tar.gz": "3e1c7d6472c34e7210e3605be4bac9ddd32f613d44297dc50cf2d067e720c4a9", -"openblas64_-v0.3.7-527-g79fd006c-manylinux2014_ppc64le.tar.gz": "a0885873298e21297a04be6cb7355a585df4fa4873e436b4c16c0a18fc9073ea", -"openblas-v0.3.7-527-g79fd006c-manylinux2014_s390x.tar.gz": "79b454320817574e20499d58f05259ed35213bea0158953992b910607b17f240", -"openblas64_-v0.3.7-527-g79fd006c-manylinux2014_s390x.tar.gz": "9fddbebf5301518fc4a5d2022a61886544a0566868c8c014359a1ee6b17f2814", -"openblas-v0.3.7-527-g79fd006c-manylinux1_i686.tar.gz": "24fb92684ec4676185fff5c9340f50c3db6075948bcef760e9c715a8974e4680", -"openblas-v0.3.7-527-g79fd006c-manylinux1_x86_64.tar.gz": "ebb8236b57a1b4075fd5cdc3e9246d2900c133a42482e5e714d1e67af5d00e62", -"openblas-v0.3.7-527-g79fd006c-manylinux1_i686.tar.gz": "24fb92684ec4676185fff5c9340f50c3db6075948bcef760e9c715a8974e4680", -"openblas-v0.3.7-527-g79fd006c-manylinux1_x86_64.tar.gz": "ebb8236b57a1b4075fd5cdc3e9246d2900c133a42482e5e714d1e67af5d00e62", -"openblas-v0.3.7-527-g79fd006c-manylinux1_i686.tar.gz": "24fb92684ec4676185fff5c9340f50c3db6075948bcef760e9c715a8974e4680", -"openblas-v0.3.7-527-g79fd006c-manylinux1_x86_64.tar.gz": "ebb8236b57a1b4075fd5cdc3e9246d2900c133a42482e5e714d1e67af5d00e62", + "openblas-v0.3.7-527-g79fd006c-win_amd64-gcc_7_1_0.zip": + "7249d68c02e6b6339e06edfeab1fecddf29ee1e67a3afaa77917c320c43de840", + "openblas64_-v0.3.7-527-g79fd006c-win_amd64-gcc_7_1_0.zip": + "6488e0961a5926e47242f63b63b41cfdd661e6f1d267e8e313e397cde4775c17", + "openblas-v0.3.7-527-g79fd006c-win32-gcc_7_1_0.zip": + "5fb0867ca70b1d0fdbf68dd387c0211f26903d74631420e4aabb49e94aa3930d", + "openblas-v0.3.7-527-g79fd006c-macosx_10_9_x86_64-gf_1becaaa.tar.gz": + "69434bd626bbc495da9ce8c36b005d140c75e3c47f94e88c764a199e820f9259", + "openblas64_-v0.3.7-527-g79fd006c-macosx_10_9_x86_64-gf_1becaaa.tar.gz": + "093f6d953e3fa76a86809be67bd1f0b27656671b5a55b233169cfaa43fd63e22", + "openblas-v0.3.7-527-g79fd006c-manylinux2014_aarch64.tar.gz": + "42676c69dc48cd6e412251b39da6b955a5a0e00323ddd77f9137f7c259d35319", + "openblas64_-v0.3.7-527-g79fd006c-manylinux2014_aarch64.tar.gz": + "5aec167af4052cf5e9e3e416c522d9794efabf03a2aea78b9bb3adc94f0b73d8", + "openblas-v0.3.7-527-g79fd006c-manylinux2010_x86_64.tar.gz": + "fa67c6cc29d4cc5c70a147c80526243239a6f95fc3feadcf83a78176cd9c526b", + "openblas64_-v0.3.7-527-g79fd006c-manylinux2010_x86_64.tar.gz": + "9ad34e89a5307dcf5823bf5c020580d0559a0c155fe85b44fc219752e61852b0", + "openblas-v0.3.7-527-g79fd006c-manylinux2010_i686.tar.gz": + "0b8595d316c8b7be84ab1f1d5a0c89c1b35f7c987cdaf61d441bcba7ab4c7439", + "openblas-v0.3.7-527-g79fd006c-manylinux2014_ppc64le.tar.gz": + "3e1c7d6472c34e7210e3605be4bac9ddd32f613d44297dc50cf2d067e720c4a9", + "openblas64_-v0.3.7-527-g79fd006c-manylinux2014_ppc64le.tar.gz": + "a0885873298e21297a04be6cb7355a585df4fa4873e436b4c16c0a18fc9073ea", + "openblas-v0.3.7-527-g79fd006c-manylinux2014_s390x.tar.gz": + "79b454320817574e20499d58f05259ed35213bea0158953992b910607b17f240", + "openblas64_-v0.3.7-527-g79fd006c-manylinux2014_s390x.tar.gz": + "9fddbebf5301518fc4a5d2022a61886544a0566868c8c014359a1ee6b17f2814", + "openblas-v0.3.7-527-g79fd006c-manylinux1_i686.tar.gz": + "24fb92684ec4676185fff5c9340f50c3db6075948bcef760e9c715a8974e4680", + "openblas-v0.3.7-527-g79fd006c-manylinux1_x86_64.tar.gz": + "ebb8236b57a1b4075fd5cdc3e9246d2900c133a42482e5e714d1e67af5d00e62", + "openblas-v0.3.10-win_amd64-gcc_7_1_0.zip": + "e5356a2aa4aa7ed9233b2ca199fdd445f55ba227f004ebc63071dfa2426e9b09", + "openblas64_-v0.3.10-win_amd64-gcc_7_1_0.zip": + "aea3f9c8bdfe0b837f0d2739a6c755b12b6838f6c983e4ede71b4e1b576e6e77", + "openblas-v0.3.10-win32-gcc_7_1_0.zip": + "af1ad3172b23f7c6ef2234151a71d3be4d92010dad4dfb25d07cf5a20f009202", + "openblas64_-v0.3.10-macosx_10_9_x86_64-gf_1becaaa.tar.gz": + "38b61c58d63048731d6884fea7b63f8cbd610e85b138c6bac0e39fd77cd4699b", + "openblas-v0.3.10-manylinux2014_aarch64.tar.gz": + "c4444b9836ec26f7772fae02851961bf73177ff2aa436470e56fab8a1ef8d405", + "openblas-v0.3.10-manylinux2010_x86_64.tar.gz": + "cb7988c4a015aece9c49b1169f51c4ac2287fb9aab8114c8ab67792138ffc85e", + "openblas-v0.3.10-manylinux2010_i686.tar.gz": + "dc637801dd80ebd6394ea8b4a97f8858e4224870ea9214de08bebbdddd8e206e", + "openblas-v0.3.10-manylinux1_x86_64.tar.gz": + "ec1f9e9b2a62d5cb9e2634b88ee2da7cb6b07702d5a0e8b190d680a31adfa23a", + "openblas-v0.3.10-manylinux1_i686.tar.gz": + "b13d9d14e6bd452c0fbadb5cd5fda05b98b1e14043edb13ead90694d4cc07f0e", + "openblas-v0.3.10-manylinux2014_ppc64le.tar.gz": + "1cbc8176986099cf0cbb8f64968d5a14880d602d4b3c59a91d75b69b8760cde3", + "openblas-v0.3.10-manylinux2014_s390x.tar.gz": + "fa6722f0b12507ab0a65f38501ed8435b573df0adc0b979f47cdc4c9e9599475", + "openblas-v0.3.10-macosx_10_9_x86_64-gf_1becaaa.tar.gz": + "c6940b5133e687ae7a4f9c7c794f6a6d92b619cf41e591e5db07aab5da118199", + "openblas64_-v0.3.10-manylinux2014_s390x.tar.gz": + "e0347dd6f3f3a27d2f5e76d382e8a4a68e2e92f5f6a10e54ef65c7b14b44d0e8", + "openblas64_-v0.3.10-manylinux2014_ppc64le.tar.gz": + "4b96a51ac767ec0aabb821c61bcd3420e82e987fc93f7e1f85aebb2a845694eb", + "openblas64_-v0.3.10-manylinux2010_x86_64.tar.gz": + "f68fea21fbc73d06b7566057cad2ed8c7c0eb71fabf9ed8a609f86e5bc60ce5e", + "openblas64_-v0.3.10-manylinux2014_aarch64.tar.gz": + "15e6eed8cb0df8b88e52baa136ffe1769c517e9de7bcdfd81ec56420ae1069e9", + "openblas64_-v0.3.10-win_amd64-gcc_7_1_0.zip": + "aea3f9c8bdfe0b837f0d2739a6c755b12b6838f6c983e4ede71b4e1b576e6e77", } IS_32BIT = sys.maxsize < 2**32 + def get_arch(): if platform.system() == 'Windows': ret = 'windows' @@ -53,10 +102,11 @@ def get_arch(): # What do 32 bit machines report? # If they are a docker, they can report x86_64 if 'x86' in ret and IS_32BIT: - arch = 'i686' + ret = 'i686' assert ret in ARCHITECTURES, f'invalid architecture {ret}' return ret + def get_ilp64(): if os.environ.get("NPY_USE_BLAS_ILP64", "0") == "0": return None @@ -64,6 +114,7 @@ def get_ilp64(): raise RuntimeError("NPY_USE_BLAS_ILP64 set on 32-bit arch") return "64_" + def get_manylinux(arch): if arch in ('x86_64', 'i686'): default = '2010' @@ -75,13 +126,13 @@ def get_manylinux(arch): return ret -def download_openblas(target, arch, ilp64): +def download_openblas(target, arch, ilp64, is_32bit): ml_ver = get_manylinux(arch) fnsuffix = {None: "", "64_": "64_"}[ilp64] filename = '' - headers = {'User-Agent': ('Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 ; ' - '(KHTML, like Gecko) Chrome/41.0.2228.0 ' - 'Safari/537.3')} + headers = {'User-Agent': + ('Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 ; ' + '(KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.3')} if arch in ('aarch64', 'ppc64le', 's390x', 'x86_64', 'i686'): suffix = f'manylinux{ml_ver}_{arch}.tar.gz' filename = f'{BASEURL}/openblas{fnsuffix}-{OPENBLAS_LONG}-{suffix}' @@ -91,7 +142,7 @@ def download_openblas(target, arch, ilp64): filename = f'{BASEURL}/openblas{fnsuffix}-{OPENBLAS_LONG}-{suffix}' typ = 'tar.gz' elif arch == 'windows': - if IS_32BIT: + if is_32bit: suffix = 'win32-gcc_7_1_0.zip' else: suffix = 'win_amd64-gcc_7_1_0.zip' @@ -100,7 +151,11 @@ def download_openblas(target, arch, ilp64): if not filename: return None req = Request(url=filename, headers=headers) - response = urlopen(req) + try: + response = urlopen(req) + except HTTPError: + print(f'Could not download "{filename}"', file=sys.stderr) + raise length = response.getheader('content-length') if response.status != 200: print(f'Could not download "{filename}"', file=sys.stderr) @@ -112,16 +167,18 @@ def download_openblas(target, arch, ilp64): sha256_returned = hashlib.sha256(data).hexdigest() if key not in sha256_vals: raise ValueError( - f'key "{key}" with hash "{sha256_returned}" not in sha256_vals') + f'\nkey "{key}" with hash "{sha256_returned}" not in sha256_vals\n') sha256_expected = sha256_vals[key] if sha256_returned != sha256_expected: + # print(f'\nkey "{key}" with hash "{sha256_returned}" mismatch\n') raise ValueError(f'sha256 hash mismatch for filename {filename}') print("Saving to file", file=sys.stderr) with open(target, 'wb') as fid: fid.write(data) return typ -def setup_openblas(arch=get_arch(), ilp64=get_ilp64()): + +def setup_openblas(arch=get_arch(), ilp64=get_ilp64(), is_32bit=IS_32BIT): ''' Download and setup an openblas library for building. If successful, the configuration script will find it automatically. @@ -135,24 +192,25 @@ def setup_openblas(arch=get_arch(), ilp64=get_ilp64()): _, tmp = mkstemp() if not arch: raise ValueError('unknown architecture') - typ = download_openblas(tmp, arch, ilp64) + typ = download_openblas(tmp, arch, ilp64, is_32bit) if not typ: return '' if arch == 'windows': if not typ == 'zip': - return 'expecting to download zipfile on windows, not %s' % str(typ) + return f'expecting to download zipfile on windows, not {typ}' return unpack_windows_zip(tmp) else: if not typ == 'tar.gz': return 'expecting to download tar.gz, not %s' % str(typ) return unpack_targz(tmp) + def unpack_windows_zip(fname): with zipfile.ZipFile(fname, 'r') as zf: # Get the openblas.a file, but not openblas.dll.a nor openblas.dev.a lib = [x for x in zf.namelist() if OPENBLAS_LONG in x and - x.endswith('a') and not x.endswith('dll.a') and - not x.endswith('dev.a')] + x.endswith('a') and not x.endswith('dll.a') and + not x.endswith('dev.a')] if not lib: return 'could not find libopenblas_%s*.a ' \ 'in downloaded zipfile' % OPENBLAS_LONG @@ -161,6 +219,7 @@ def unpack_windows_zip(fname): fid.write(zf.read(lib[0])) return target + def unpack_targz(fname): target = os.path.join(gettempdir(), 'openblas') if not os.path.exists(target): @@ -171,6 +230,7 @@ def unpack_targz(fname): extract_tarfile_to(zf, target, prefix) return target + def extract_tarfile_to(tarfileobj, target_path, archive_path): """Extract TarFile contents under archive_path/ to target_path/""" @@ -194,6 +254,7 @@ def extract_tarfile_to(tarfileobj, target_path, archive_path): tarfileobj.extractall(target_path, members=get_members()) + def make_init(dirname): ''' Create a _distributor_init.py file for OpenBlas @@ -228,33 +289,51 @@ def make_init(dirname): DLL_filenames.append(filename) if len(DLL_filenames) > 1: import warnings - warnings.warn("loaded more than 1 DLL from .libs:\\n%s" % - "\\n".join(DLL_filenames), + warnings.warn("loaded more than 1 DLL from .libs:" + "\\n%s" % "\\n".join(DLL_filenames), stacklevel=1) """)) + def test_setup(arches): ''' Make sure all the downloadable files exist and can be opened ''' def items(): + """ yields all combinations of arch, ilp64, is_32bit + """ for arch in arches: - yield arch, None - if arch not in ('i686'): - yield arch, '64_' + yield arch, None, False + if arch not in ('i686',): + yield arch, '64_', False + if arch in ('windows',): + yield arch, None, True + if arch in ('i686', 'x86_64'): + oldval = os.environ.get('MB_ML_VER', None) + os.environ['MB_ML_VER'] = '1' + yield arch, None, False + # Once we create x86_64 and i686 manylinux2014 wheels... + # os.environ['MB_ML_VER'] = '2014' + # yield arch, None, False + if oldval: + os.environ['MB_ML_VER'] = oldval + else: + os.environ.pop('MB_ML_VER') errs = [] - for arch, ilp64 in items(): + for arch, ilp64, is_32bit in items(): if arch == '': continue - + if arch not in arches: + continue target = None try: try: - target = setup_openblas(arch, ilp64) + target = setup_openblas(arch, ilp64, is_32bit) except Exception as e: - print(f'Could not setup {arch}:') - print(str(e)) + print(f'Could not setup {arch} with ilp64 {ilp64}, ' + f'32bit {is_32bit}:') + print(e) errs.append(e) continue if not target: @@ -290,28 +369,31 @@ def test_version(expected_version, ilp64=get_ilp64()): get_config = dll.openblas_get_config64_ else: get_config = dll.openblas_get_config - get_config.restype=ctypes.c_char_p + get_config.restype = ctypes.c_char_p res = get_config() print('OpenBLAS get_config returned', str(res)) if not expected_version: expected_version = OPENBLAS_V check_str = b'OpenBLAS %s' % expected_version.encode() print(check_str) - assert check_str in res, '%s not found in %s' %(expected_version, res) + assert check_str in res, f'{expected_version} not found in {res}' if ilp64: assert b"USE64BITINT" in res else: assert b"USE64BITINT" not in res + if __name__ == '__main__': import argparse parser = argparse.ArgumentParser( - description='Download and expand an OpenBLAS archive for this ' \ + description='Download and expand an OpenBLAS archive for this ' 'architecture') parser.add_argument('--test', nargs='*', default=None, - help='Test different architectures. "all", or any of %s' % ARCHITECTURES) + help='Test different architectures. "all", or any of ' + f'{ARCHITECTURES}') parser.add_argument('--check_version', nargs='?', default='', - help='Check provided OpenBLAS version string against available OpenBLAS') + help='Check provided OpenBLAS version string ' + 'against available OpenBLAS') args = parser.parse_args() if args.check_version != '': test_version(args.check_version) diff --git a/tools/pypy-test.sh b/tools/pypy-test.sh index e24d7a99d..32b7968d8 100755 --- a/tools/pypy-test.sh +++ b/tools/pypy-test.sh @@ -33,7 +33,7 @@ wget -q https://downloads.python.org/pypy/pypy3.6-v7.3.1-linux64.tar.bz2 -O pypy mkdir -p pypy3 (cd pypy3; tar --strip-components=1 -xf ../pypy.tar.bz2) pypy3/bin/pypy3 -mensurepip -pypy3/bin/pypy3 -m pip install --upgrade pip setuptools wheel +pypy3/bin/pypy3 -m pip install --upgrade pip pypy3/bin/pypy3 -m pip install --user -r test_requirements.txt --no-warn-script-location echo diff --git a/tools/travis-before-install.sh b/tools/travis-before-install.sh index e468dd932..1446a8bad 100755 --- a/tools/travis-before-install.sh +++ b/tools/travis-before-install.sh @@ -29,7 +29,7 @@ gcc --version popd -pip install --upgrade pip +pip install --upgrade pip setuptools!=49.2.0 wheel # 'setuptools', 'wheel' and 'cython' are build dependencies. This information # is stored in pyproject.toml, but there is not yet a standard way to install @@ -41,7 +41,7 @@ pip install --upgrade pip # A specific version of cython is required, so we read the cython package # requirement using `grep cython test_requirements.txt` instead of simply # writing 'pip install setuptools wheel cython'. -pip install setuptools wheel `grep cython test_requirements.txt` +pip install `grep cython test_requirements.txt` if [ -n "$DOWNLOAD_OPENBLAS" ]; then pwd diff --git a/tools/travis-test.sh b/tools/travis-test.sh index 225cbfa0c..f6804f7fe 100755 --- a/tools/travis-test.sh +++ b/tools/travis-test.sh @@ -75,6 +75,7 @@ run_test() if [ -n "$USE_DEBUG" ]; then export PYTHONPATH=$PWD + export MYPYPATH=$PWD fi # pytest aborts when running --durations with python3.6-dbg, so only enable |
