pax_global_header00006660000000000000000000000064141425542540014520gustar00rootroot0000000000000052 comment=b1a45843c3be49cd232d3c78315d2291a830284f cykhash-2.0.0/000077500000000000000000000000001414255425400131515ustar00rootroot00000000000000cykhash-2.0.0/.github/000077500000000000000000000000001414255425400145115ustar00rootroot00000000000000cykhash-2.0.0/.github/workflows/000077500000000000000000000000001414255425400165465ustar00rootroot00000000000000cykhash-2.0.0/.github/workflows/python-package.yml000066400000000000000000000030351414255425400222040ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: cykhash on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: strategy: matrix: python-version: [3.5, 3.6, 3.7, 3.8, 3.9, '3.10', pypy-3.6, pypy-3.7] platform: [ubuntu-latest, macos-latest, windows-latest] exclude: # macos has issues with numpy for some versions - python-version: pypy-3.6 platform: macos-latest - python-version: pypy-3.7 platform: macos-latest runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install . pip install pytest pip install numpy pip install cython - name: Test with pytest run: | (cd tests/unit_tests && pytest) - name: Run doctests repository run: | pytest --ignore=tests --doctest-glob=*.md --doctest-glob=*.pyx --doctest-glob=*.pxi -vv --doctest-continue-on-failure - name: Run doctests installation run: | (cd tests && python run_installed_doctests.py) cykhash-2.0.0/.gitignore000066400000000000000000000025421414255425400151440ustar00rootroot00000000000000# asv_bench-files tests/asv_bench/results tests/asv_bench/env # created pxi from template src/cykhash/maps/*.pxi src/cykhash/sets/*.pxi src/cykhash/unique/*.pxi # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so *.c # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ p2/ p3/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ cykhash-2.0.0/LICENSE000066400000000000000000000020501414255425400141530ustar00rootroot00000000000000MIT License Copyright (c) 2018 realead Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cykhash-2.0.0/MANIFEST.in000066400000000000000000000001131414255425400147020ustar00rootroot00000000000000recursive-include src *.pxi *.pxd *.pyx *.pxi.in recursive-exclude src *.c cykhash-2.0.0/README.md000066400000000000000000000317571414255425400144450ustar00rootroot00000000000000# cykhash cython wrapper for khash-sets/maps, efficient implementation of `isin` and `unique` ## About: * Brings functionality of khash (https://github.com/attractivechaos/klib/blob/master/khash.h) to Python and Cython and can be used seamlessly in numpy or pandas. * Numpy's world is lacking the concept of a (hash-)set. This shortcoming is fixed and efficient (memory- and speedwise compared to pandas') `unique` and `isin` are implemented. * Python-set/dict have big memory-footprint. For some datatypes the overhead can be reduced by using khash by factor 4-8. ## Installation: The recommended way to install the library is via `conda` package manager using the `conda-forge` channel: conda install -c conda-forge cykhash You can also install the library using `pip`. To install the latest release: pip install cykhash To install the most recent version of the module: pip install https://github.com/realead/cykhash/zipball/master ## Dependencies: To build the library from source, Cython>=0.28 is required as well as a c-build tool chain. See (https://github.com/realead/cykhash/blob/master/doc/README4DEVELOPER.md) for dependencies needed for development. ## Quick start #### Hash set and isin Creating a hashset and using it in `isin`: # prepare data: >>> import numpy as np >>> a = np.arange(42, dtype=np.int64) >>> b = np.arange(84, dtype=np.int64) >>> result = np.empty(b.size, dtype=np.bool) # actually usage >>> from cykhash import Int64Set_from_buffer, isin_int64 >>> lookup = Int64Set_from_buffer(a) # create a hashset >>> isin_int64(b, lookup, result) # running time O(b.size) >>> isin_int64(b, lookup, result) # lookup is reused and not recreated ### `unique` Finding `unique` in `O(n)` (compared to numpy's `np.unique` - `O(n*logn)`) and smaller memory-footprint than pandas' `pd.unique`: # prepare input >>> import numpy as np >>> a = np.array([1,2,3,3,2,1], dtype=np.int64) # actual usage: >>> from cykhash import unique_int64 >>> unique_buffer = unique_int64(a) # unique element are exposed via buffer-protocol # can be converted to a numpy-array without copying via >>> unique_array = np.ctypeslib.as_array(unique_buffer) >>> unique_array.shape (3,) ### Hash map Maps and sets handle `nan`-correctly (try it out with Python's dict/set): >>> from cykhash import Float64toInt64Map >>> my_map = Float64toInt64Map() # values are 64bit integers >>> my_map[float("nan")] = 1 >>> my_map[float("nan")] 1 ## Functionality overview ### Hash sets `Int64Set`, `Int32Set`, `Float64Set`, `Float32Set` ( and `PyObjectSet`) are implemented. They are more or less drop-in replacements for Python's `set`. Furthermore, given the Cython-interface, efficient extensions of functionality are easily done. The biggest advantage of these sets is that they need about 4-8 times less memory than the usual Python-sets and are somewhat faster for integers or floats. As `PyObjectSet` is somewhat slower than the usual `set` and needs about the same amount of memory, it should be used only if all `nan`s should be treated as equivalent. The most efficient way to create such sets is to use `XXXXSet_from_buffer(...)`, e.g. `Int64Set_from_buffer`, if the data container at hand supports buffer protocol (e.g. numpy-arrays, `array.array` or `ctypes`-arrays). Or `XXXXSet_from(...)` for any iterator. ### Hash maps `Int64toInt64Map`, `Int32toInt32Map`, `Float64toInt64Map`, `Float32toInt32Map` ( and `PyObjectMap`) are implemented. They are more or less drop-in replacements for Python's `dict` (however, not every piece of `dict`'s functionality makes sense, for example `setdefault(x, default)` without `default`-argument, because `None` cannot be inserted, also the khash-maps don't preserve the insertion order, so there is also no `reversed`). Furthermore, given the Cython-interface, efficient extensions of functionality are easily done. Biggest advantage of these sets is that they need about 4-8 times less memory than the usual Python-dictionaries and are somewhat faster for integers or floats. As `PyObjectMap` is somewhat slower than the usual `dict` and needs about the same amount of memory, it should be used only if all `nan`s should be treated as equivalent. ### isin * implemented are `isin_int64`, `isin_int32`, `isin_float64`, `isin_float32` * using hash set instead of arrays in `isin` function has the advantage, that the look-up data structure doesn't have to be reconstructed for every call, thus reducing the running time from `O(n+m)`to `O(n)`, where `n` is the number of queries and `m`-number of elements in the look up array. * Thus cykash's `isin` can be order of magnitude faster than the numpy's or pandas' versions. #### all, none, any, and count_if * siblings functions of `isin_XXX` are: * `all_XXX`/`all_XXX_from_iterator` which return `True` if all elements of the query array can be found in the set. * `any_XXX`/`any_XXX_from_iterator` which return `True` if at least one element of the query array can be found in the set. * `none_XXX`/`none_XXX_from_iterator` which return `True` if none of elements from the query array can be found in the set. * `count_if_XXX`/`count_if_XXX_from_iterator` which return the number of elements from the query array can be found in the set. * `all_XXX`, `any_XXX`, `none_XXX` and `count_if_XXX` are faster than using `isin_XXX` and applying numpy's versions of these function on the resulting array. * `from_iterator` version works with any iterable, but the version for buffers are more efficient. ### unique * implemented are `unique_int64`, `unique_int32`, `unique_float64`, `unique_float32` * returns an object which implements the buffer protocol, so `np.ctypeslib.as_array` (recommended) or `np.frombuffer` (less safe, as memory can get reinterpreted) can be used to create numpy arrays. * differently as pandas, the returned uniques aren't in the order of the appearance. If order of appearence is important use `unique_stable_xxx`-versions, which needs somewhat more memory. * the signature is `unique_xxx(buffer, size_hint=0.0)` the initial memory-consumption of the hash-set will be `len(buffer)*size_hint` unless `size_hint<=0.0`, in this case it will be ensured, that no rehashing is needed even if all elements are unique in the buffer. As pandas uses maps instead of sets internally for `unique`, it needs about 4 times more peak memory and is 1.6-3 times slower. ### Floating-point numbers as keys There is a problem with floating-point sets or maps, i.e. `Float64Set`, `Float32Set`, `Float64toInt64Map` and `Float32toInt32Map`: The standard definition of "equal" and hash-function based on the bit representation don't define a meaningful or desired behavior for the hash set: * `NAN != NAN` and thus it is not equivalence relation * `-0.0 == 0.0` but `hash(-0.0)!=hash(0.0)`, but `x==y => hash(x)==hash(y)` is neccessary for set to work properly. This problem is resolved through following special case handling: * `hash(-0.0):=hash(0.0)` * `hash(x):=hash(NAN)` for any not a number `x`. * `x is equal y <=> x==y || (x!=x && y!=y)` A consequence of the above rule, that the equivalence classes of `{0.0, -0.0}` and `e{x | x is not a number}` have more than one element. In the set these classes are represented by the first seen element from the class. The above holds also for `PyObjectSet` (this behavior is not the same as fro Python-`set` which shows a different behavior for nans). ### Examples: #### Hash sets Python: Creates a set from a numpy-array and looks up whether an element is in the resulting set: >>> import numpy as np >>> from cykhash import Int64Set_from_buffer >>> a = np.arange(42, dtype=np.int64) >>> my_set = Int64Set_from_buffer(a) # no reallocation will be needed >>> 41 in my_set True >>> 42 not in my_set True Python: Create a set from an iterable and looks up whether an element is in the resulting set: >>> from cykhash import Int64Set_from >>> my_set = Int64Set_from(range(42)) # no reallocation will be needed >>> assert 41 in my_set and 42 not in my_set Cython: Create a set and put some values into it: from cykhash.khashsets cimport Int64Set my_set = Int64Set(number_of_elements_hint=12) # reserve place for at least 12 integers cdef Py_ssize_t i for i in range(12): my_set.add(i) assert 11 in my_set and 12 not in my_set #### Hash maps Python: Creating `int64->float64` map using `Int64toFloat64Map_from_buffers`: >>> import numpy as np >>> from cykhash import Int64toFloat64Map_from_buffers >>> keys = np.array([1, 2, 3, 4], dtype=np.int64) >>> vals = np.array([5, 6, 7, 8], dtype=np.float64) >>> my_map = Int64toFloat64Map_from_buffers(keys, vals) # there will be no reallocation >>> assert my_map[4] == 8.0 Python: Creating `int64->int64` map from scratch: >>> import numpy as np >>> from cykhash import Int64toInt64Map # my_map will not need reallocation for at least 12 elements >>> my_map = Int64toInt64Map(number_of_elements_hint=12) >>> for i in range(12): my_map[i] = i+1 >>> assert my_map[5] == 6 #### isin Python: Creating look-up data structure from a numpy-array, performing `isin`-query >>> import numpy as np >>> from cykhash import Int64Set_from_buffer, isin_int64 >>> a = np.arange(42, dtype=np.int64) >>> lookup = Int64Set_from_buffer(a) >>> b = np.arange(84, dtype=np.int64) >>> result = np.empty(b.size, dtype=np.bool) >>> isin_int64(b, lookup, result) # running time O(b.size) >>> assert np.sum(result.astype(np.int)) == 42 #### unique Python: using `unique_int64`: >>> import numpy as np >>> from cykhash import unique_int64 >>> a = np.array([1,2,3,3,2,1], dtype=np.int64) >>> u = np.ctypeslib.as_array(unique_int64(a)) # there will be no reallocation >>> assert set(u) == {1,2,3} Python: using `unique_stable_int64`: >>> import numpy as np >>> from cykhash import unique_stable_int64 >>> a = np.array([3,2,1,1,2,3], dtype=np.int64) >>> u = np.ctypeslib.as_array(unique_stable_int64(a)) # there will be no reallocation >>> assert list(u) == [3,2,1] ## API See (https://github.com/realead/cykhash/blob/master/doc/README_API.md) for a more detailed API description. ## Performance See (https://github.com/realead/cykhash/blob/master/doc/README_PERFORMANCE.md) for results of performance tests. ## Trivia * This project was inspired by the following stackoverflow question: https://stackoverflow.com/questions/50779617/pandas-pd-series-isin-performance-with-set-versus-array. * pandas also uses `khash` (and thus was a source of inspiration), but wraps only maps and doesn't wrap sets. Thus, pandas' `unique` needs more memory as it should. Those maps are also never exposed, so there is no way to reuse the look-up structure for multiple calls to `isin`. * `khash` is a good choice, but there are other alternatives, e.g. https://github.com/sparsehash/sparsehash. See also https://stackoverflow.com/questions/48129713/fastest-way-to-find-all-unique-elements-in-an-array-with-cython/48142655#48142655 for a comparison for different `unique` implementations. * A similar approach for sets/maps in pure Cython: https://github.com/realead/tighthash, which is quite slower than khash. * There is no dependency on `numpy`: this library uses buffer protocol, thus it works for `array.array`, `numpy.ndarray`, `ctypes`-arrays and anything else. However, some interfaces are somewhat cumbersome (which type should be created as answer?) and for convenient usage it might be a good idea to wrap the functionality so objects of right types are created. ## History: #### Release 2.0.0 (09.11.2021): * Implementation of `any`, `all`, `none` and `count_if` * Hash-sets are now (almost) drop-in replacements of Python's sets * Breaking change: iterator from maps doesn't no longer returns items but only keys. However there are following new methods `keys()`, `values()` and `items()`which return so called mapvies, which correspond more or less to dictviews (but for mapsview doesn't hold that "Dictionary order is guaranteed to be insertion order."). * Hash-Maps are now (almost) drop-in replacements of Python's dicts. Differences: insertion order isn't preserved, thus there is also no `reversed()`-method, `setdefault(key, default)` isn't possible without `default` because `None` cannot be inserted in the map * Better hash-functions for float64, float32, int64 and int32 (gh-issue #4). * Breaking change: different names/signatures for maps * supports tracemalloc for Py3.6+ * supports Python 3.10 #### Release 1.0.2 (30.05.2020): * can be installed via conda-forge to all operating systems * can be installed via pip in a clean environment (Cython>=0.28 is now fetched automatically) #### Release 1.0.1 (27.05.2020): * released on PyPi #### Older: * 0.4.0: uniques_stable, preparing for release * 0.3.0: PyObjectSet, Maps for Int64/32 and also Float64/32, unique-versions * 0.2.0: Int32Set, Float64Set, Float32Set * 0.1.0: Int64Set cykhash-2.0.0/distribute/000077500000000000000000000000001414255425400153275ustar00rootroot00000000000000cykhash-2.0.0/distribute/upload_to_pypi.sh000066400000000000000000000010311414255425400207050ustar00rootroot00000000000000 DISTRO="cykhash-2.0.0.tar.gz" cd .. # clean up rm -rf dist pip uninstall cykhash # create sdist python setup.py sdist # test: cd dist pip install "$DISTRO" cd ../tests sh run_unit_tests.sh sh run_doctests.sh #clean up pip uninstall cykhash # test distro cd .. twine check dist/"$DISTRO" # test upload to test twine upload --repository-url https://test.pypi.org/legacy/ dist/"$DISTRO" cd tests sh test_install.sh from-test-pypi cd .. # upload to pypi twine upload dist/"$DISTRO" cd tests sh test_install.sh from-pypi cykhash-2.0.0/doc/000077500000000000000000000000001414255425400137165ustar00rootroot00000000000000cykhash-2.0.0/doc/README4DEVELOPER.md000066400000000000000000000021211414255425400165230ustar00rootroot00000000000000# cykhash for developers (i.e. future me) ## Dependencies: Essential: * Cython>=0.28 because verbatim C-code feature is used * build tool chain (for example gcc on Linux) Additional dependencies for testing: * `sh` * `virtualenv` * `pytest` * `numpy`, `pandas`, `perfplot` for performance tests * `asv` for asv_bench testing (performance tests) ## Testing: For testing of the local version in an virtual environment run: sh test_install.sh in the `tests` subfolder. For testing of the version from github run: sh test_install.sh from-github For keeping the the virtual enviroment after the tests: sh test_install.sh local keep To install and running tests in currently active environment: sh test_in_active_env.sh For comparing performance of the HEAD with upstream/master: sh run_asv_bench.sh you might want to reduce the number of tests - see examples in `run_asv_bench.sh` For running doc-tests: sh run_doctests.sh ## Uploading to PyPi: Follow procedure in `distribute/upload_to_pypi.sh`, i.e. cd distribute sh upload_to_pypi.sh cykhash-2.0.0/doc/README_API.md000066400000000000000000000114261414255425400156720ustar00rootroot00000000000000# cykhash API: Note: This is an incomplete list of functionality. ## Isin * `isin_int64`, `isin_int32`, `isin_float64`, `isin_float32` * the signature is `def isin_int64(int64_t[:] query, Int64Set db, uint8_t[:] result)`. `query` and `result` must have the same size, otherwise an exception is raised. * Running time is `O(len(query))`s. ## Unique * `unique_int64`, `unique_int32`, `unique_float64`, `unique_float32` * returns an object which implements the buffer protocol, so `np.ctypeslib.as_array` (recommended) or `np.frombuffer` (less safe, as memory can get reinterpreted) can be used to create numpy arrays. * differently as pandas, the returned uniques aren't in the order of the appearance. * the signature is `unique_int64(buffer, size_hint=0.0)` the initial memory-consumption of the hash-set will be `len(buffer)*size_hint` unless `size_hint<=0.0`, in this case it will be ensured, that no rehashing is needed even if all elements are unique in the buffer. * `unique_stable_int64`, `unique_stable_int32`, `unique_stable_float64`, `unique_stable_float32` order the elements in order of their appearance. ### Sets Following classes are defined: * `Int64Set` for 64 bit integers * `Int32Set` for 32 bit integers * `Float64Set`for 64 bit floats * `Float32Set`for 32 bit floats * `PyObjectSet`for arbitrary Python-objects with Python interface: * `__len__`: number of elements in the set * `__contains__`: whether an element is contained in the set * `add`: adds an element to set * `discard`: remove an element or do nothing if element is not in the set * `__iter__`: returns an iterator through all elements in set with Cython interface: * `contains`: checks whether an element is contained in the set * `add` : adds an element to the set * `discard` : remove an element or do nothing if element is not in the set * `get_iter`: returns an iterator with the following Cython interface: * `has_next`:returns true if there are more elements in the iterator * `next` :returns next element and moves the iterator #### Utility functions for sets: The following functions are available: * `XXXXSet_from(it)` - creates a `XXXXSet` from an iterable, with `XXXX` being either `Int64`, `Int32`, `Float64`, `Float32` or `PyObject`. * `XXXXSet_from_buffer(buf, size_hint=0.0)` creates a `XXXXSet` from an object which implements buffer interface, with `XXXX` being either `Int64`, `Int32`, `Float64`, `Float32` or `PyObject`. Starting size of hash-set is `int(size_hint*len(buf))` unless `size_hint<=0.0`, in which case it will be ensured that no rehashing is needed. * `isin_xxxx(query, db, result)` evaluates `isin` for `query` being a buffer of the right type, `db` - a corresponding `XXXXSet`, and result a buffer for with 8bit-itemsize, `xxxx` being either `int64`, `int32`, `float64`, `float32` or `pyobject`. ### Maps Following classes are defined: * `Int64toInt64Map` for mapping 64 bit integers to 64bit integer/floats * `Int32toInt32Map` for mapping 32 bit integers to 32bit integer/floats * `Float64toInt64Map`for mapping 64 bit floats to 64bit integer/floats * `Float32toInt32Map`for mapping 32 bit floats to 32bit integer/floats * `PyObjectMap`for arbitrary Python-objects as key/values with Python interface: * `__len__`: number of elements in the map * `__contains__`: whether an element is contained in the map * `put_intXX/get_intXX`: setting/retrieving elements with XX=32 or 64 bits integer * `put_floatXX/get_floatXX`: setting/retrieving elements with XX=32 or 64 bits float * `__setitem__/__getitem___`: parameter `for_intXX` in the constructor deceides whether elements are intepreted as int or float (XX = 32 or 64 bits) * `discard`: remove an element or do nothing if element is not in the map * `__iter__`: returns an iterator through all elements in map with Cython interface: * `contains`: checks whether an element is contained in the map * `put_intXX/get_intXX,put_floatXX/get_floatXX` : setting/getting elements in the map * `discard` : remove an element or do nothing if element is not in the map * `get_iter`: returns an iterator with the following Cython interface: * `has_next`:returns true if there are more elements in the iterator * `next` :returns next element and moves the iterator #### Utility functions for maps: The following functions are available: * `TypeXXtoXXMap_from_typeXX_buffer(keys, vals, size_hint=0.0)` - creates a `TypeXXtoXXMyp` from buffers with `Type` either `Float` or `Int`, `XX` either 32 or 64 and `type` either `float` or `int`. Starting size of hash-map is `int(size_hint*min(len(keys), len(vals)))` unless `size_hint<=0.0`, in which case it will be ensured that no rehashing is needed. * `PyObjectMap_from_object_buffer` for keys, values as objects. cykhash-2.0.0/doc/README_PERFORMANCE.md000066400000000000000000000121011414255425400170110ustar00rootroot00000000000000# Performance: Run `sh run_perf_tests.sh` in tests-folder to reproduce. numpy and pandas need to be installed in addition to be able to run the performance tests. The easiest way is to call first `sh test_instalation.sh p3 local keep` and then activate it via `. ../p3/bin/activate` and only then call the performance tests, or to run `sh test_in_active_env.sh` to install in developer-mode in the current environment. #### Memory consumption of sets: Peak memory usage for N int64-integers (inclusive python-interpreter): 10^3 10^4 10^5 10^6 10^7 python2-set 6MB 6MB 13MB 62MB 502MB python3-set 8MB 9MB 17MB 79MB 588MB cykhash (p3) 10MB 10MB 10MB 26MB 147MB i.e. there is about 4 time less memory needed. #### pd.unique() Warning: cykhash's version of `unique` doesn't not return uniques in order of appearance (which pandas' version does!). `cykhash.unique` returns a object with buffer-interface, which can be used for creation of numpy-arrays via `np.frombuffer`. The implementation of pandas' `unique` uses a hash-table instead of hash-set, which results in a larger memory footprint. `cykhash.unique_int64` (or `unique_int32`, `unique_float32`, `unique_float64`) uses much less memory and is also faster. See also question https://stackoverflow.com/questions/51485816/curious-memory-consumption-of-pandas-unique for more details. ![1](imgs/unique_time_comparison.png) There is no int32-version in `pandas`, such there is no difference to 64bit. `int32`-version of `cykhash` is twice as fast as `int64`-version, which is about factor 1.66 faster than the pandas' version. Run `tests/perf_tests/khashunique_vs_pdunique.py` to generate the plot. Peak memory overhead (additional needed memory given as `maximal_overhead_factor`) for unique (run `tests/perf_tests/run_memory_unique_test.sh` to generate the numbers). i.e. additional memory needed is `N*maximal_overhead_factor` N pd.unique() cykhash.unique_int64 1e6 6.24 2.18 2e6 6.19 2.15 4e6 6.35 2.16 6e6 4.86 1.44 8e6 6.25 2.16 9e6 7.50 1.90 While `pd.unique` needs up to 8 times the original memory, `cykhash` at most 3 times the original memory. For the special case of `int32` the overhead-factor of `pd.unique` more than doubles because it doesn't have a dedicated int32-version and data must be converted to int64. More precise the worst case scenario (while still without triggering rehashing) for `cykhash.unique_int64`, the overhead-factor can be `1.3*2*(1+1/64)=2.64` where: * 1.3 due to the fact that rehashing happens when more than 77% of buckets are occupied. * 2 due to the fact, that khash doubles the size every time * 1+1/64 that one bit is used for flags #### isin Compared to pandas' `isin`, which has a linear running time in number of elements in the lookup. cykhash's `isin` has a `O(1)` in the number of elements in the look-up: n pandas(#look-up=10^n) cykhash(#look-up=10^n) 2 0.0009466878400417045 0.0008094332498149015 3 0.0011027359400759451 0.001505808719957713 4 0.001315673690114636 0.0005093092197785154 5 0.007601776499941479 0.00031931002013152465 6 0.11544147745007649 0.000292295379913412 7 0.7747500354002114 0.00047073251014808195 #### PyObjectSet: There are no advantages (others that nans are handled correctly, more about it later) to use khash-version for normal Python-objects. A big issue, is that `PyObject_Hash` works well with Python's dict/set implementation, but less so with khash. By running `python tests/perf_tests/pyobjectset_vs_set.py` we get the following results: ![2](imgs/set_vs_pyobjectset_fixed_hash.png) It is only 2 times slower than Python-set for inserting elements (but still somewhat not quite linear?. However it has also a negative impact on `insert`: ![1](imgs/set_vs_pyobjectset_only_insert_fixed_hash.png) As conclusion: * `contains` are almost equally fast for `set`/`PyObjectSet`. * `insert` is slightly slower for `PyObjectSet`, preallocation should be used whenever possible. * `discard` is quite slow for `PyObjectSet`. Insertion of Python-integers is 50% slower than the insertion of 64bit integers (see `tests/perf_tests/object_vs_int64_via_buffer.py`): ![1](imgs/buffer_objects_int64.png) Using `PyObjectSet_from_buffer` is about 2.5 times faster than from iterator and 3 times faster than using `add`. For best performance use `Int64Set` or, even better, `Int32Set`, which is about factor 2 faster than the 64bit-version. #### PyObjectMap: There are similar observation as for `PyObjectMap`, see `map_object_vs_int64_via_buffer.py` and `pyobjectmap_vs_dict.py` in 'tests/perf_tests`-folder. The results are slightly worse (all above discard): ![1](imgs/map_vs_pyobjectmap_fixed_hash.png) and ![1](imgs/map_vs_pyobjectmap_only_insert_fixed_hash.png) Also here the performance of the 64bit/32bit variant much better: ![1](imgs/buffer_objects_int64_map.png) cykhash-2.0.0/doc/imgs/000077500000000000000000000000001414255425400146555ustar00rootroot00000000000000cykhash-2.0.0/doc/imgs/buffer_objects_int64.png000066400000000000000000002015211414255425400213720ustar00rootroot00000000000000PNG  IHDR5sBIT|d pHYsaa?i9tEXtSoftwarematplotlib version 3.0.3, http://matplotlib.org/ IDATxwXW7ҖRIĆ j{Kb O#nlyLb$Ѩ Qc4b7*,!×Rޟ̜s=vc1ƘЪ0c c1Ƙ1cLpc1a8d102ciNc14 '1c@c1 c1Ƙ1cLpc1a8d102ciNc14 '1c@c1 c1Ƙ1cLpc1a8d102ciNc14 '1c@c1 c1Ƙ1cLpc1a8d102ciNc14 '1c@c1 c1Ƙ1cLpc1a8dGGG 2rD~ 88֏p #55S'zgmmsa~AABBB=j( 7n@HH'&t1&ѵkci .88"5jR)5j>YYYB)S*mm۶h"899AOOM6̙3rC__[n b૯RiO X 4IIIHMME&M!!!DDS7زe :t###u?2߇?lmm'''T8#++ zzzT%%%A$ q,((y}}}vԭw% 6 ׇ;~'a{xx8F ݻpluK1V1F "@~)9r6l@N2\Bh۶mJ_N諯"""BA>>>C}EGGӺuԴiSo>LǏ' RRRm߾]i5vO' m۶dhhHtڷo͞=~w***(@SLsѹsjo׮]>cG7|CB #ںu+=z-[Fb^#G$;;;JϟOzzzMDDd``@6lǏӡChմyJMz;QϞ=iϞ=E~~~J+33V\)|Oʎ-33S3'9s(߹s';v뼼MI*ғ'O$fڵJC(,,LX@"׏R)Q RnnRYf>=|BCC T,@AAAȬYqƕ'###{u~KudccCՕFKz[&www*))QZ?dӟǫX[i+-3:::8~nHHH3gyyy0qD~wPu9zhرcJ۶m:({˗/ע";v #GJKKϠAPTT~7lov5#Ν;7n~dgg9tz 8pĉaee۷ 9{aJ}߰pBƢPc۷$|w^<HNN~cc/NVVVJ:::033CNNnpttم#??3g@GGGVF$J>Rl޼JAeeeV ___|{.yXXXK.lقcС]UshѢ 1V4cvU5~PZ#244޽{T~@6lPZ_6I$466t oM:tJl c*,GhH@+M:lllq5ݏ.]PΝ/$Te@ X]/kѢ 4ze>\1?1 ~_~~:>3tcƌQ*׸qcL0[lU~ ,@^^<==qUJmll0l0;v@LL ֬Y _Gٳ'OGGG}7|TnӦMD*Wtc OX- FHHTUO>-[^'Wp\777hQD]]]ܽ{Wc5 ~c0"c1a8du.55" TF8::bӦMDp4nXX[=ըsnc5HvvvȀkW[ t1e14'AֆttxRCP͛7(%%-ݻnܸASN%cccV*ODD7… )11._L޽{ ?>Pxx8ݾ}N:E۶m#" .:z(eddPNNPFh޼ytmqݻw(**R)ӝ;w(::)88233 m߾222(33&$$7|CW^7oŋI___'ՕΞ=K.]ݻD"7\.'WWW'OݝPDDD}P'Ǐ'Ծ}{۷oSvvv1!"ڸq#_ѱcǨUV4}ta۩QFrPPuAXrvv={ҩS֭[g:{,r:y$}8''lmm)44222(##^JFFFqFy&9sOsS%LF$e DYCGĭ}Gr w9x~k N_Au|Y>'__"qBD~A $oooaƆVX[oE3f իW KJJ֖֬YT,\d ӇrP'==Prr2X,ѳDƖ[gϞrJu?Y[[ &]iӦ m޼ ?^؞H$ȑ#MwޥG\.~ `e/K###Odff&,WVٺu+SNNʾr=c""ח>CuN"---*,,Tܔc!C&ъ+߯Yfѓ'5Nx+W׮]nu ׯ\.Ǵi0ydlذعs'֯_ý{Ԟ'\[n'..NT*Uv<~۷hjj ???_~x1fX[[ xpr9PPPRGHH:{HKK$&& (ӺukY-(R]~Xr%nܸ<044O|SSrNJ+믿JuHx͛7<7]Ob?n3Z"V8C x* #,[ 8}4LҥK+P(mHNN.oWw-cY #]# vݭ7'uH$@WF :057 ?^eEL8۷oX,{'\ JӧѫW/ٳgѹsg6ʔ"..f*?;v޽{===-ZD"c0uTeur6wwwcѢE֭~Gt;vDrr27o^atuum"NF x)RSS...(--ťKx%''B6m -- ܹ݃sjLu_\t X~駟:ߟ/>|XUsaĉx}xuׯ_97ꋸC9wxrcܹ˗yf6/L:...3g(Of͚ ۷oGBBʌɯ -Z 6n܈Gagƌضm}̟?}6vލm۶A__ ,󡧧OOOdee2e ,,, H[[[Ç ða`ccdܼy&LJҐ!C`ggѣGCKK W^ŵkװ|rf;v 0114͛71tPD"|gP(}AV0`L6 aaaA``U~ZP(xNĿLU1i֬JKKyf :gΜ7|S~U7nV\#F`ժUF||}ZhÇ񴲲>>ter )  :|0]~&NH&&&CDDǎ#ԵkW:}4]|7oN^^^Կ|2>>P(HIIyxx>c'!3cŕ+03W}´I uI{jU({RcWСC!Xb;Je^]Q(0`֭[kkk_,dzgϢs*m)--E\\f͚Un:v숽{ѱ٤-ZD"c0uTeur6wwwcѢE֭~Gt;vDrrʃŸ[nyӧx6;ҥKu>===ر#_^i\c7\ľ[arV8dJOOܹs˗/cX~}ԩpqq9sF~ 4k nnnؾ};sNr_}Zhlܸ=n%hƌضm}̟?}6vލm۶A__ ,󡧧OOOdee2e ,,, H[[[Ç ða`ccdܼy&L,]C F ---\z׮]Ax(ڸ1ޯoMWYeff~?I&x>޽{!JaggW3L.CdA$.]i NYYZZaaar;rʔ)Ö-[ kԶm[7\n;VH$uJؿ8ic=Ccq-zRl黅T-`ېdׯ,,,w^"==*e/^0od2zDDؓk.A)IKl v<ޏ+LrJL<ưGXXP Hooo~"22ppp@=СC}=}ǏǶm}\pp0!Hٳg d2ϟM]tbI&!77""U;cccXYYGffRÇe˖H$ݻ7RSSU = 0rHT [n 0zh+b~~~9r$6l[[[lٲʘ@NNƍ[[[]vصk}Rn022T*Ř1c2annQF *qjj*z 011H$g?nk׮3$ :t耽{*a熱7QK.Ŋ?VJ1q~'B!"@OaaWyW_nj30}t$%%.\8z(222~nݺa̙+V\ \̙31x`*ݻw/6n܈["99;v쀫}ҤI8s vލWb0`nݺݻcӦMJ@FF͛Wd2,[ W\Add$RRR1j( 4 :u*.\ɓ'cƌHHH@޽|rݾ}?<($$$`̙Je~wܼyGC SN8tO|V#FÇq Ν;;vP_ŨQ0x`رcP+vvvطo 99/K,۱e\~s|'Nxvn}]/_.0&&!v$DZi.ZX刽\@* ƍTXX(ӍV#ϯqyyyٳ>aB ڲe WjUV$it%ڵkRHHPf׮]*_ZlI2r9=zr9ݾ}D"?J۷/-ZoN5ֱ… _<qF""&mmmJKK_~Ѕ [n4~xH5ǏW'ӧOI__Ξ=Twʔ)4n8"<|Pɋ>.d2$LV/oޤ\zE˓smIPWeߚ*=?6O$J ,,,N:ŋeϮ̞=;v쀾~m=pvvƇ~C O|2-[9qܹÇ-4@bb"v4[nJm$&&{q*UP(,suuUNLr9VX FFF*ݿڴiƍ#11}VFU1.ύ7PTT~)/[bb"tRaMADؕ SLâhi2$:$HrZe <ZZ$WܞHBQikkkB[[[XC&!..ԩ].ɓ/Q\\ ;;;$''#&&1117ok8q ڈSq߿?v؁&M -- >>>dtuTWYR|rchhTF_7nĦMЮ];"00P8Q3 _/NS}_ѴiSmbXcX^#v$`@w[Z8C""5U(UZ -WNkSyzzBMX[[COO}ŵkהL4 [Ƃ F"`ذa2d&LΝ;ڵkpww\.Gff&zYa^WeիW W.]TM6TZwy2/{q*iiiwlllΝ0٣<ԩS>|8>[nE~iiiiHOObt moǎäITSmڴX,FZZ*[U熱s|S7~ Yׂ$ O̞=7oį+W 1440#<<⯿ž={ H-[b0aߏ\xk֬5舧OرcFAAAboo===l޼8<ď>wܹsJek͛/Ucĉr N:3VVVQ'&͛7GLL Ξ=DjF1~x\|.\ %L ®]D\v k׮^ p!deeӧ066Ƽy0g|wsW_Yxq熱٥0X'ᛷĶQ}U_՝R/NzK/N)PC$,o۶HKKgϞ.]X,&gggZb_"ҥ IR244z2.]JKVVV4rHzP棏>"333HbuFPrAj޼9bٳ'}* -I$:t([Z@:t@_5ؐ>5>|(8q" 6L;RULrrrhdddDd0a >wݻ4l0244$ccc=z4ݿ_GnnnG4jԨj844H$ĉ/ZjEԤI'N~rvv5n1Q(Nr΍\]_ޡ+MRNx< HDg^V^^5j\HRmEEEHIIS*P(TڠoMGpp0"##}4)&j1yտƠATj)&b,; 0i B@jcQRx6uoMcc~}3/D5@?'ݫ7'Lc:u pӧOm۶ݻwݶu:cax"杘EX{}][/@L@0l?I_q4GUÇQRRR6KKK:؛cҏXwqJM[cSMhjԴʯRp48owZop2!Hмy /Á;!{pkpr~쵢 |hszR8Œ1X=ƍ3Q`4! zP}JcpcգZ.][G!NN@0;ı{Q8d1Ax/~95c,`TgSn?"Iqcձ vnAox瀽'!0 1CO30l$>LHs;ͅo7wpK & 9` ФU}L5G3 HT#Y) iӦooor;IIIڵ+V:41X$>L[mńod%ϒvcis@ !##5^jj*_ɇH$BDDFV<̚5 6l;#FX_ "99FFFcLSv&ĺK '9\L]&wjϽT@[ k\믅wev N}ЦMJ?y IDATbiJWx%1ahhaÆb/G[[VVVy <000P)SC_'wA=33 UL&n2V'Jbrc|?77#.mY 5A${A`` /^xDZZZϝ;cW\/ƌ?C(OOO^"""wӧS 2rƏuYfa֬Yhܸ1̰dBCCѮ];::uҥK X,T$%%{G۶m+l+pRR ###XZZvB5k֠yŰNJ+NNNwwwD"x{{bccѹsgqTzMѩS'!!!(--8::F H$,W$<<\3D"RSS 777|pvvX,>z聋/ G;$ LopqqT*ŸqPPPPiߞWZZZ/#Tq!44"cǎ 0|p aĈXjlllвeK{ EX:oWY>p(?<>gf[b_b+*RV% DZJBW-IUEiTikKde&34L2zŽs{&|sι{@FLz(6m*Fc>}:/+W@9?x@ڰƍBT]v+$\ӧOt>N'fj)n޾+R3}Γt:]'[VgϞk׮qqqBTG믿B.]Bd +qY({O*T l"N>-.ݻws]~]`q#ڵk'ڴic#00P)SFDFF/u !8zĞ={D||wh4QL}ZDFFk׮ !صkpppҥKbݢr"$$D!DBBDDD O״4gGx%f͚%lmmtb\r"**J?bL2޽{B!~g^{M:tHĈիVZۋq",XP> 9::!nݺbҤI">>^ÇFw'OOjB!,-N:ZHLLZ6_[,(NjZl߾]j\$*:W-DzW-7/=/G9!V 1A2BZ*D~YMVsqso߾=#G_ǏGо}{þrQ^=>"5Z>([EXX Zjq)1bDDDФI"""hժUV`ѯ_?.\Ⱦ}Xt)+W4\cرիWk.֯_O```x֬YC ;w.JzÆ xyyqyʖ-˲eXbZj₧'')).]PZ5j׮mܹs 6WjU̙C`` f2d(i ]nnn9Q|g2>|իcǎ[h֯_ϔ)S ~4oaÆ1uT.]dx?zž} zf>? hggk߆ P*|駆899~Ϧ-~)$|N/xS[`x<;O:*)L޽{VGܺu+snݺUTeʔw9dffmi4t:t:h Khh(aÆ1|p/^J/?Fӑ͛7qf͚qT*iܸ1OUg:ǏsArz߿Off&mڴ^/m۶mۖ޽{S~Ǐs1C72V%##To^q! ]pFU*M41Q{S~}1nnnPre>www=ޫT'?/boo㘌 .\@۶mBP^=vy]T߫G8e6Ruܣs@ʝt:VfV%=(T}de3 *@-ܡ/ȟ( `B<_O+g̞=;ݻwoff'BL|1&!,4!HOOIoV/Ғ ڵkGrr᜴gffjINN&55зr=zLVVYYYyVСaأ<<< RSSsI\t){/dƌl۶&M歷UZ6LOOyya8333QFJJJh udff(,9j:$zU* "h0j9k_FF 6dڵpqq!99Fe7[vjt8`1h]2v$kQuG^K|:Eay&WWP& ҉E/(eŅP혃"º ڮZ-U8Vc)@ 7nLtt4=z0쏎[nyGtttqw~b▗ƍcnnNtt4} >>Ey%j( JaZAdw_eQRi8}Ryx V\i 3 X9~+WJ* ꫯ_~ZVsߊZj sss-nݚ5kt1vX,E.]Dlllc.40a,Zj%zN5kEٳg .\ ({Jbi`Jc׎^^d=`c >䓂KHH>ƍ :߿-[׋խ IR[oLe 2lVia ~ Ё{] \:2JL(8<<J<,XQx|]ITT*SE!짽.IRҳ 9B~~H3+^h$# $QJLQ춑rgQ)TLi2KTCF%]?M|=sO%d(I$IOpBRfR'?۵B`lL%I$1Rmiu_D1h. #4rJL%I$)tfŏW~[n𛁥z(-'7\ ;:,P$Iu=:M\9fLi2xtӤÏl@W =?[&J$I XϷ_oS-ۃ֠(Rn2$I^jB60tBG=z +8l `㊶{8-)ycƌlٲXYYQvm,g( &899蜍7rA>L||1Ijx%أ*v,jlrYY$~;|.) vmq?đNP.ʕ+3o|D&ݹG蒒YT,L^$b$@W:>kßѣ5jgϞѣٳxm c3f ԫWyjs=f:wL۶ms]w˖-ιss :_'Oһwo:t h֬K.x}p"##;wn2g͚En8uNC[&,; kծwûyO?x( *,իčMܰ/^B笙Tٶ^3ux.FYjk~)뾻斪>SN= ؿ?޸₧>D/_޽{0`QQQ\p1cƐ̙3믉رcy^766OOOڶmJɉ6mگׯS~ ɓٵk̛7GGG E,,,roH ׭[Gzz:6m+V[opB<<< e._RIZXhiiiL6 S`~W뗯8ǎKϞ=tw^֯_O``3npss3g^ΝKpp0s!00Yf߿L/.% &p>%J2WFy2u:gڣIu#I%"X Qߵ>KZ/…xMqe$;S] ,y[yZExL ƮUɜ `1R(e0T:,JO>>y|t:CϟlٲXXXorԩ :ooo I5]vK. 4Mr)5jV%!!-Z<12:uqF>|hH~WJeVIchٲ%YYY#7W_ܹsT^8KR1JӤ1L~ox|?BeX'z$^t>àpjn$abk++q3Q3*d(;ڵ *`ee#FO>! qq͛5Պ-...hZ|}}bX[[SR%\\\0` "44Fq]Kԩ+W&55 `ccc,0k,LHHwaܸq 8[TV\I5 c  c|}}aڴi7G\י9s&]tˋ޽{T*9y$NOJ%Z\rx3MSOQS@ NF(#@ƽOsozDf&(8[x̌0ݘT8%iIz!|r)Wݺuˋݻws1^yƏO@@.ɉuѼys6lȁ c"""4h&MVZtڕ h֬#Go߾h"t IDAT ?ߧI&Ջ7|+VO`.\HFpuu|DEEQ~}gN$ܹh4ikƒ%KTFx+wҗ]?'5;{JD' ԱwWBdfbӤ Um Q9B^b8::C222r UTyCOHNNcKY:ɭ$Ia? KENJ$B6.G't KZ/6w _'w/|NB o΄fƬS=w=1/_@۷+QI~,d$IBKӤ1쾶5z2wZﭰcҳPziGW0y1o޼<_kѢ?c1G$I%_\r⃋)͘t*k.QMF Wbzs,LGn^x84umD̋x>>Dlq9r$}5kkbFJC7x u ֮,iFL=ovIzL+\Aʞ=$,\uiX?T2PziX[[)Mq%附{=c# apq7uhw~7l X 5ۛ4s=o>i;'Х 2Vz6J$I/4MNhfph~|cDlt:8{Q _82$I^ɱ 0;5{:,K [ՃA4IhHK\.9p< MTx2$IJ{CSe|Ww 6XAPI=p |Koo>'NpyR9sٳgN||<;vs$~\|p7k7"#JBubLb:1*.;sMLĢZ5֭kj,T)X#\13|XXXsN;Ì3hРL0].ϥKhܸ15j<[dfeɵLZ'O ,Bt65U^&E-N׮#"o.8Li_ !ױ:Mf YҲP:̙3iӦeFE˖-ѣ4mڔ={Pn],,rBw^Ν;… x$ aٱc| *TٳܿPfΝqvv&** GGGy79<}f׮]ٳGǧu}1 ˖-˱ڬY?>aaaT*nܸAN2d6mٳ1+++BBB mܸ@=͛5j۷oGL60Hll,663e-[;AAAtޝmT\C]}֭c֬YXF2blmm}  HRA~hRpvcI%o~?M joֵBH?q[q$^^xbdeeŋT2PzW^yB' <ޞ'N?ĉZ*[qF_~t:VZe?dڵkGZM6tǏK ٌ'3gѲڼysRSS~:+kzT*...ԯ_߰ _2ePV-Μ9Sz;wǰa *@VVVFCSX n Yz n6n͸_oíZC),4oJP:ze BG `12d-:VӑQ EBN{yJիаaCΜ9s$>}pkhxWr ?# :/[([,uݢu.Յ9Uw>6?4(JC4MˎgݺuR=6 u#IP釦'V?7 \U<8$q[.#] =zJY-=#BXNZU=O>X!N.\}r888зo_zMǎիW_֭[Qr'ƖG:uغukDS|ѮoZ>>>TV ZMTT6mbՀ+W^İsNZ-nSXXXFٲe s}xzzD۶m{,\ZjqM޽;>>>T\+Wp *TR=z4K.eܸq;s1k,&NX ~ nnnjoݺ5waѢEՋ]v?hYͯƏ;v$33?D&Nhے^svgI%4pk`갌+~-#m6u3nϝGڿSiyz>e2:A@t*MK ̌˗Nr֭>dԭ[f͚e>sٱcׯ_aÆ-[ua@… חXv܉RDPE˖-ywY&իqu={Ci777+w削ѣ4hЀ#G2l0EiФIn߾ -kfժU\ pQ&O\>|8~)ԯ_VZI91T@:#p<M ͥ/ִ'3[_ֽ{Ϙɕ{(p3j?FعL<)|KNNё\-,\r*U`Ot:qp(@GnNr+uRςh4DEEѩS'LPi7n/}k%IINt:8xևZ~ Vs/jT:u}$˕w91~Y.`I$H<>okyF)2=6.~u0t\O?O‚]n]1#G:j4kPRv\*L)-7of„ Z͛Nǎ9}iG9r}2gzaСC)+-ZĒ%Kf͚|Gk׎saoo@ΝY&{ښKҥK.]ɃG׮]sMi$t{|OvQH/~2U'sz6roĒKp.Ij,p}6G!};+W嗠բ07y`\{]]W*Ŋܚ6m*Fc>}:/'B OOO`QYF!ĝ;w 8`8&99YbϞ=\MIn%N^S2SsdPg5 U'kr!Bd$?4q/Ĺu{kycDիFƋ^1ɵ0yZo>}ܑ#Gro8ʕ+ܺu+1jp kfӦM<|,х|F^j/g+޸+͙l6_^&wu ֶ3;@iC `i_${xWzMJ²Fu*nXXTT$ה^&{.Z0uG6üpuS7c;`VPMnݰGT]DfffTy 55!(*dV$55RX,krfA&wkwXL=z&x:QOA(2оQU2z|X-cҕC^(̌^=RҼL$&O嵬.O;FѣqwwX[[駟ҥK;Fٲes]s̞=;ݻwccck=ddd`aa\s1ݻwf>ru[I!jwȅ LOtt 37C?K%U%#h,zƖ:Q2y%nLЀJex8݇ӡC(ZRɃ^^ll`n^qE=RR:3yJڗ/S~֭[9Gٻw/;w$110~ժUDGGqFs]wԩ9V@HNNˋ9H_ABB ddd`ee%'DG^dV͍u,FCtt4ڵ3NTfчINz!|]'.bO#Jtl<=%t:R{?A/͚8Պv.A(y~/6&O-,,hܸ1ð?{6/~~~DGG޽f͚PJ<==QF~/… eOT*udiigwh*TV-psFl|Y:ɭՉ9*aO*IoW`4gk3QdzO,aZ'|ߏu غULyd;MEJa׺u`eϧ1y0qD~~~]Xg |̟?Zl… ֭={СCw„ ̛75jPF ͛ Id2e>zErR׬Y3kOΌ3V7o1[`` =D|}}ٽ{a@WWWv|oFnݺ|4hP֧$I*|WUW mZ{_L~Uӹz_^pѮ#IR"@ѣG3z<_ۿ}zW^O,OPBHH᧟~*h$I/u Mc~4^/閞VN#ju0ZBHXJV|<6>>xLU:F$WI%Irep5*J f͠{xtZ?|.zo2ƛg/ܞ?ʕ#0{ $I-kzL&p~pf5/Yw!PX[EieekH $INXVYjqzS 6BPÊ BVsF^'I?az3I*n2$I R)L;8߻?L.=@yd9.,@ܼ&pe?EOm+xNuÆ._) s V($IJR?/=FuN?~o- Bq<'o6i"]PwhI2|'<`ҥ8::>eִZm$Iϱ?xy,k]Se<7oXʒ[PrνD~ _oCO,% _~q=W@$IR N"d8*R2' Z5LV\u hHk\.) qE Ɗ^L'-$)))F$I*>)/JxT9N}V-y㹋M=x t Z6 [ߦƈZ|D$%t%&`7o&ݪ )ᬾ9Pl pmlWp.N*SzBQB|z7na;00'''5kƵk׌$Id|?LjU{ڔn/\ɟ΃S]W& yamm #GXb-ՕߨJ$Iơ:V &_w.aM| MTm J~UjI.w~Dh4ضjI;KRqz.ุ8Wի.͛7uƌO$I2u 3uj`Ϥ3/7/@U(u.1n͛O3XT`Z4^̒dBϕq=*Vݻ ~VVV5@I$p i )X,7պ:,9GBFX;CuPs~ŋIٵ =ncP$Y$3l׮ÇQF?Ν;?Prec'I$޸IY5,mtj`2v; > .-qZ J%N}z6~> ٪$3=~m4 fYt$IPcTj~y8I*+trrbŊϞ=I$IN&@0o`?V IDATe3đ_`0xx, S3NEdu p}v( 1I$#}h SO5\&=zPm׏{e'D<'`ƌ AV?wP$IRiuZVXɺShٔ[}3\D;`f Cр|. ew4 _.bEj,\}FE$lN[lɹs]ahI$h$ :ġXg OLY Ӏs5x)挳g=wiǎ`8ԯW/ߟ]P$I*7ooU{aGF2tUVȺ;˖oACai˰a 0xI*J$I/k|pҳ)g[6aqc갌U=_9υk-_Vs/j;vcd˗@[%d(I괬8OO} /2VeLw"deC*_~^Nm 16);+WW8Q)xIz1P$}u7nﷴRj6uXq{>)`=CVO=Ede`,]x)X%)_;Xf W\ȑ#TTKRJu+<$IRNz_̂V+6^SO{oܞ7^ Sk޼#R%+nwQTǿ)  lX|Q z/R AQP_l?$T!"%͖1! Y6|fvawsr{1_uwlN?!,`Qիx'XdI3yd'5_)1n8.g^ƁyAx$:oFAN28Aoz)3'yFll|Y|^{[j QA,Ji[9:;J!jw0'bNxU=WܫyEu&e Fųa+f3i}O[+1%$6-,J5jiРA۶mEj2B#cćtՁЮd_f|"DQۿ}Ρó#.8'kPipM &BܞE )SxWEQ>_|AHHׯvBQӘwW#rW=t  {}C /_A?uvg+x޾<ڳeȑFNJvv6C N:Zg}1 !D~pUa:ku$k(\=_ `c@_crN$.8#GU )q_ Q,J;w.?3B'''[%8!niL;ȫl97{S8M<U aLL$>,oEA~5E8?5k1OM][!>~ZnmxZ[cͼUtXw.,|9Nz(( ?Lܒbbph 3q|BaQؼysrrrBT;&UGW:IJ˪x?C3APs YBVzp8E+ !Ģp͚5L>sҪU+ =f*Kӧ1e\;T~F= ڨ+(f% @YdI㹹{)(AS~R{]ZZ(iiiq3yyyw}YHGQrMkVV[)I:Sn]]IQ*<EyMQ( 甬_U|R9լrYs|~J/]eD>;Q? zqO^h>}̟;8  ۷/aaaҧOu:ݺu#22ѣGsQ\Vm۶ҦM/_NKY\qFUGWFNV =TMO6p ޯF}e@>;Q^͔8߿?vvv</u JFɟSN1fYfOa֭W_}ѣ;դ둝]!T'O<ONネM޾"=x@ u"7s}- t^͘1'lS^=cO ݻHMM$ף(&E5vq\s.u]겢 z4Xm>ƣ=Y=I4evU$ף(&EYF:ε;*5ޯkw I0IBZi4NN2^#G-{:Na5zԔy+O`'bWA !ʊE ȑ#ׯ~~2229rdĂ vZb֭=q111hYĦM={6s̡ql޼`@SرcIIICܹ`@Plmm:t(999tЁݻwieBT3N3\ͺ#:/O>?23(G>!'`H583qlݺB1h\|wwn;c/Rd8pMh4̛7y;;;/_K̏O)zXcM=+xJ{4qGɎl< GEh(!DSm۶h44 z~L&W fBXنlo} @:Yڥj+̏~#R|AѠy%*^{q틋Kc4lذ`J!jJ){p!4wc[LZ\MI8٠׭S !*B7| 2x`$(!(O/Y2y:%:, 9KK\ڃkޜY3q NQ,8|p@/2-L<2!(i4EcW:݃udr(PUs# `3hlp*,Jϝ; /Pd!Z!*ʱcL;kYװ2 m1BRSH"ɻObZ؎{C1K\ !j&#F`kkˏ?HZBd6щx\5>-+:4)&m$᭷0 .w]gh{XBaQxq9BͭBfXub=̜spw͙W֡͟ػ;EFUpBƢE$&&Z;!(S.cvlsqudf h<Ȼ|ed ΌoG<M{*8:!DefQtRNJpp0sO5ܬBXd`U||cnVpd1ge7l@gl|k@Wu{3âW^K;oɤL?b%Ƅ?PE{3ˢvBau[/ledpwcA'VB9ǏB`b¿M.-| W !nݺY;!c /d-wK,K qqįXAS+$ӻ!JǢp޽|k׮#wmѠ{_fL1j-0\7l q(99 5ƁY(8}#v޽Ⱦd )—}cĈ!]BxZ(BƎ/ p*n?VpB̢0%%ж`رc̙3ŋ[%0!(4}oFbdq[-&ǯOm~Rk;v?W TQY׻wot:&Lȑ#wBıcL;جXl͢nJ̘DB*R )mOL3jBTNV&ٳlR!e2Xz`9!\Z=N;xf#xԯ`ՒE `6mh4(Rhǎ裏Bg?} 0?FQ׽l||0:2 .S|`4YUV(.j*A !ĿLŽɩOhٌn4roT)5wא`2kpG&8| 2Z=]! !1 +WPNBIaީ˹ i>'V/$qۘp SW"`BP}>M+8j!Dug"X/^ɱVB ?ٸY{VtX%uq!ϝ@״)3gܩd'æ_Ճ[Y N(򩩩D߯FП;;sh˷jwMMltn !IzgΜ޽{>|8۷og„ l߾\m&k !lY&\֣}J)3#OP s|_( vSx6RoֺCB06駟ذa=cǎI&u]U|BDQ8+~[A9?G?t]WthͤmB[ap5iπ'Q? O !DY+UxUZh@`` KefIӧ1'b?3vcax:xVpd}(q=7hitτaPH:Z[xh>tzȄBT lή`ggg%Yeھi˹Mj7~Raz+Hߺ >cshn\M0dkmub*&h!WPQFNN˘1c$~"BT[&ŬZeݖһeEvSH҇F?ra48QOg [!nTpᅶy#9☱ 2l*]EQHߺ+0^SϜCИӡڔBQR%6l(85{Tmq67~ú' &QjoT\)zXЧ׸O]#Y* Bj%ϔG0>=)ͽ5 +60&$Vi[qtQxK_ kxGݮnwYIQH((11L;SIxn"69ssIS־9;o$~ T; !D% xGXX ߣ*B1IV]0{ 96m?)g%N xZTB !P!ŇCoǒ.KpȊʊ$.t9ӧU qohŬi6P%P xcn\ !$B3gg /hs^el*Y%lٳć.'k~@{x ZtuYI(}0 )j!3 !rn[lɏ%]*rnXVMwߩkgyl=o߇~lѷ-n!IVȹz97SFI'Qz\o׿B0) +Bq3 !HF\vvZ;&ĐC*rnJ^)$qL))8o)8n}s:OoεB! ⎘&~b[u]F ?(BƎĿå< =n _S* IDATWA%$BXB@!☾o:gff9G_Jx{kx ƶ_G? *ߺ8j!({ !,=̎])sˋ&nj2wqt9$ sYn|0j!(? !J%ϔ[G⿧ ^wJ1) #f,0@y5n߀/Kյ|5Z9:OB*J@!D]JĔ=S8N3݄ _͜Cƍ$~\wo$tM` HKݮ^(H((?\D1 ;/{b2e o@nݺ.{PPIm궓<&y*٤Ba-ƚ5khԨk׎}o-ZhѢ[l)(̛7ڵkH9ydmzڴiFx`q!*ېͬ&lǾOQ2!'6{xRb^%tZošj򧵅Gԕ=$BTc"ܼy3Ǐg֬Y;v.]пbbb= [FCf,x6lfe"%f2+q9܊Z1yyy9rӧߧO"##=L0о}$ҧOu:ݺu#22ѣGǨQprrmz}Q  C ^m]oVUr=*kOey콲nuf7yT7R?G~uj u-ęϋ;c< VFc&VY(D%dqB2.$s(:\#nN \Z+ O1LOlllw̥K6bƌCxmc aEܹD %ˤݪJGQּ&ht%l؏_񿌕%ш{w&'ƍIxaub\&4YEڃM+S g(&UH_iJp.MC ;6 w1/`"i޽0]`7t76UP9힝Vnaujn&瓈8 Iq؛M jC=N"nR$cǎeر>Km 8޴=Füy7o^aÆ(r~EcC΋V[=b4$.D۴o e®yj+tKeq>DF%/Ζj׹M\nY%*J !N!'/6EQܽ+ȋA}&NµO0Z y6A7տ`@!2Tp[RRJ = jC&>V&lI(D5w*SNR%-~Q*=r~Pr~SoxzxzMIVPk}uCeP]E.D͐7r2Emg9rJ&W|*uEْPjrn+`6ҮKi_> S^L oEƶht:F)Xs}:Ͽ͇{~_!D+u#[j pԭ$JMan\~ =`A<<))$]K[ O<7˟Dr`28NcI1%e2+Fd+uu SMj썏TVw Q+N'>';S³͞-A\?aT9wI84kVp3|JPw2\EQguJ]Jכxǁy2Ar " Մldx1+f5$[h/禘ͤ xd'g/Ӻ\=n{7~KCVZrJgA`ZT(Ibbo:GB02_-3"ϜV-ƏДf|^5&YֹAi`[~ QUHS Qy,6 |L3C/hJސ! {W! @\p+n\S7|"gb3 =.$қ#w BR߭mδ!6U}:5^C1zz!Ei]R.>BRL-D5U|nkvnRW$BTAiL3ygbM0edz?E-r{?c_ gYuZK^Y=@P+u#9t5uI*uP*ܜ4Nt gÞe\J^)6f T۷[.]c9g)^Nbc^.@R Qy]ԍ/ڸ]+M@!,C. ?ޯ==s{ҥN?(dI[+1\>0ɓpѣtSʘMpSBVw1xZ=v!*V:1PMdM]Q$ 8t{Vel o>wX!~Y(9ؖ+# lL4~!ФJ]QI(D%( =_VYl$9]r} >B4+Wh~FŹt]pku[ݧ) QѤRWT% QI0'b{.g,wUǘH»W`2V*v~Ő Vþ`4p09\|J݈~9 T*B@!*s3V_͜Mƍ$szkʥG&MDפIS8# NRb"Xy^RWTM QF=n Ym9ͼJm(&~K۫1&k:jߔ)8wx Ɵm Zĵ:=eZQX_qJ TꊪB@!*جX S2JS۹MUkUt$)§>季oa4\]֯?6rxz ::d9~3:@qyì+/jfrnb%y5IhkJh^6D00F _Ps*uBDi+uxSSƝ Q$ f#!FňΓgӧa9~9'>Ah,BQuZ꾆]heQBʝV !ʖ$Bܡ(f$u^{1K_wo};#6..'ldZa5؇R+DP &>=)&Ϝ+3;F'ØB⚵lh4?$o]@ef%ϋi]L"J݈j'BT- afGX1 :ٿTssIS֭ÜKܥ ~'̲LFu.!7UI;'0,oWT+u#fRW JMTꊒSBhoLr3 ,%/* @&OƥGwN z9iVOc1N;^q>QWrmRWܒd`̻Z^;w½yNn2P MF~M<`0w{]QQ-]J^u'o3ϠYn>ԊaˠA:a4[޶rJZ۹:=Ko]غu+%j'/X/%>3|q0)ުut(r%*Jx.c^Y(̟?u֑Bxwiٲ%/^d…޽Xj׮?ϬY}Ϗ];Ĝ9\˺ oOw:tM?c(W>2Ӻ5]QfMݠ>yFsIsHOPH֣ou\^EscScccc6Z fll8uv4Qp-(_ITUxxݿ'TFyU3}v'a0w2(_G~`6vyyQQ$.';"'ޯO'_fQ;P|0Ӻ{&Iz\H`t2QRxR <E@/ZvJ]yU51R=w7'bȽuWm^<^jk{@lll. jO`ZnիWѣ:ubݺuUӡcggWfl**+/fřdu~ 1L<n>1%w%eӦ su<`}]רӺhmhMvo }MJRۦA0WJ]y1DZbɱdDqh%2jUmR.f-nբު-n3w_GI:o@{{{ڵkGxxxi\0`@tԉBwIP45" pڶm c ҥK ιr =z]vlذV*2ƓyFu0~` M$?= )So`fc34Br7rԂ oW@#o\tuZ)Bnp i7V:vZܼY6\}Jꅸkĉ :0f F:u *ǍG׮]Yt) g׮]߿Po?`6mJӦM ɉ!Cj_ݩ_>˗/'!Ӄ(hfн^w&>>7='su>ht͚uhy &#Aܟ>FjwW?֥OK)v1.%sjzJNxKn02Sr UҦݐVm!f4ߥCwxTULˤ $Q,EE@E 6dYJ@ERvUlWݵ.k,$BH=!OϜ3L2ɼɓ3w=hB7NҒ"|'Q\\u!77Gc&;;7:7|pl߾Vիѭ[7ر˖-^B P;y HOOGzz::vȻFĭؘ _Fj~Mw1=6ϯ3J3Ǥ*ޗ1 1+Zd|Y|0?:ޞ^ędEEQEE6K#ZWm94"2< rϟ<~c?8z'8̙9sĝwqV^TX;|-Gp]= na8w?mg`B0Z8ScH,EjV Ju;1ᾈELsiҲjk{Ue_+ EM< j i4c ?\;7-zr2Q?wc KH*EJf R2Jp@ )+~HŴVYV{:&jQU # IC) 7AkB&aŘg&_˗y _b^^Zi3)'I!*uB-$$auj s"/ O|qy~c͛G5n2#سν_*$\:7:aސ=k%Iojk{RS& V֞*Uk6!h!@ҪUx؛x}8kS]/(**bC7ͨ_.UcFjf n.R;֡caAWahk{S5RjpR@ i5)y)X}x5r9g`PHj\=O G=%͛G5nduc)ReN9PPoD!&T{^V E"~}[dBHq:_ ź0$f.7of$!mQ V5ceR)w*Ǣ00ܾ2wP/tB~~ zՖ4*?/x_K!m"wTRN{cx9edBɷߢmUVc/6NETïc(-fjQS o}e^`5m<iPQd@Yet*75ZDȥfkj5թZj($wD =Wno2 { #Fpۿ^/3 ^o`*ӧd& goG 6r,1{}m25+С4O=Ua?祔8-ZT-!V1؍)Qb(3qs .`fT%GD\ ɍ~0U|'PT,Ā>Š_ EZc2XP|C 1Rwgz&׉%Bh R'H 4A *ܒ4@rjsn÷Ow `W(=`͙6^ϏjFKdB^|51~˾ Ӵr,6 ņZZ=PGUJ o/{{ Yc6aVB"Wsj&8ax',:H?E>HɄoE6y F UU0j`ږRTKr|VC}]RT6[i' N .)WʯFuUCW!X ɓ(ۛХy=rySO@ !~CfKdjs9@KT'osKw/e(R=xVb ѣ-!Y-uU5qU{k# mRJ.HmjxR::ALi,lj~rPHnZ 綠+q0edۋPH^x"lyB$ jù޹DzJPu.#Pm6(lF|;V)e[CYJ)[ʖx"fdכR+8)ꑶ@MP"[S'i+zTOA )?#*hGrPXPI`$!!/֔~f3pzۣ`lvj)8q z3p _~ ]_285pvUïXkDbN/Fҕ"dtcPupʱMVsiZ{r*F])[Ⱦ=/l6#s@YQ>O,PzޛJ]k\ R%? #[+ct>/oOaƲ`HwCҥP-zEo5Zp4N/…Jyꍘp?{9_p9ƀ"=%MJz9_(xz%ciL dTT*Hh*i(lON_=ܿ^.&KXmV|s}-zHRyx J_qp0:,\ͤGK^'ۋ8W8؞mC5+g!)2Xt{1[Ft.~P{c W4@ 2>ǭVJ)$K2"5rRY-L7X*s*'K'qPPJx$ ۋ򜚕}}= L)%3gAX o@ `\ΜW-CpG{w=渲pt )C[z;jc8wpzR2Kn=û#@uX)[}lu,-y T)1R}lhU՝#w )UٌsBǢ8sǿ~y^V{)Cj'?gY<)D:o/2fςC_ds̎os=1[V!qz$-zA[b "%MHOjL6FQɝSR,-ϟh)*#R)՚Jy))JȝB`{qfs稧(qvp=w k""0Q=UlPǦ /$8_՚? PݖGn z"$])F^BJ&Ю~/=TMd[P5'OnVKXe 1|r*Sf2KZFC)VuR2ZgLΊUR R3 ۃ4@ N998/ q=%,(OfN/-j>} I*0}7apZG_+^=UX1?4r׿m6< +PY ]ӺLʽ WC7 ›FܑdҤZs߸4knyLH}A]hBq)U-+&Ou灂* JdV0fթL!FjF{*_M\,fsE %`q.LzHV yDFᔼpU*qR% NnMa)όIk1$d%`KȴXIBHB䭆iZTf{tm?bc(MJ/FbT)#pSpVUf+ȪyD,ڃpoF@Z3?shz^*XL Pr:2\8ӈ˔J ̋ !mmc\=>].ؙ #1(xHMF $aC[QZ.~aѭ€}nW*"  @`5oAkvU ?Y*w訪 ½E BFYZ,ƆF R(iRG&S(_\}BvޚIdtBi(lrOW']>H+GɈRZTH$1B|`%pi#[/T sCUI8g oȭ01  B3e2:̟Ih:ӿ|nw1 f+e:JtN9j/~!D;@!bVW~E(9ԐO2 #(JPuʙġ>JT:iT Aޭ,n r(ll6}ŏjaR<6X5G^153*&#;[wSӇ-m +RXc%8|iYe0Y+kvPr#|CC.dqx: 2+PS[=Wj/Y /lVu:#ouUiDYQ!>[~\kC}Q:*K!mm@uI"0wXg1,%؊ݣgK]8e]xX`(\gFFq2(BF伲6 ûn 읭o9(լDy%5#{h+fQ2_32j?n+Rx$^rGՆyPDA!x *Gwb!-PK{3v cn|tX2=t \M1}o~6Ɋ*d9][P/D }5W`681CHWwGP]-ɼଡ7Nc@ B &8mY-n ?#K=F^M^!v1TGa>r|2;{cOgS 81}7|.lZe[@ ]@RP"PnBqEj\rNُI:%Eq9]-q\&QUԐ)68ߐ0Z@!Y(lUⵊy 1$,F^OSG-?[bt ~Oǧ"\>].=pWW)"-2u.b]5/ z(UT6F&h:Uk.! h{/D00hЖAG`q@u[zK,KtNRu^J !; ۘ\伿ދ٬x6׭4!PHxu|oL êhf 6NM]^I'x * &<{ʷQ1 +)mzY!!Y @ 4¤ס(Sg?/DʟV+:SSG!sp۶m_\[n=So;wbոr u_'O3ưvZ|'(--Ő!C_~\R,Z?`ĉxs&Uq*a'c/ ɚu=:H8'0 P\[T1# 9>3f`FfE&&``1`1A `*gI$)T?(87Z'~BH]nرضmF?ӟp9t٩}rr2|I_'OƏ?SСC2d`˖-x뭷_gϞذaƌ/BVf̘ׯcϞ=sb֬Y7jbw`}/݆";r1V,HbCeEzV$ (/,fCffcؘfpyeqZ, OH"q*o9r+; B!-z =y֭[믿?ƍoݺcƌ++VuV|w`a֭Xr%Lꫯo/"Ο?={?O?Æ ŋѫW;+r)BL+ʼԸ}@O]!?&@_Q}c5^ ؤŤ5RgBcA\c \Ĺ !Z=4LHMMyǎ$INNƒ%Kxƍ[222cre2F$HNNF?:t(4 \FZVTT4WެOWm@EN ̌p$Uj5Ԏ [n_ߒQ8>qF}Ó>j"(_.((yyy._`ϮdeeqmXnܸk׮u:B57"sF-Ā B C(A(A$A$B@BB,J-*?PbR[3g'ΨOljwT*Ettsbb"5Æ sjҥ ymL&86lqQ͑#GP^^^B!>K.ŬYaÆO>Avv6͛={6¸/ƽދ͛7Gݻ`O7@=УGP(1cO>?~<^x|e`~aXL!rEO֭Cnn."##p@vv66>|8oߎUVa֭v[ѻl2z̟?+o-V O8zjB! ̟?wynNq<^O  ..qqq_[%BiZ} !B($B0B!x !B< B!@B!C !BB!PH!af'1hkft:TTT@"g'ΨOQ8>]{Qx *++:uj;!BHsUVVBѴm l6ܸqjE]QQN:ڵknkEΨOQ8>qF}Cee%BCC!zl8B;v1C63g'ΨOQyZx_5 { !B<B!F7A\D Sp3g'|B!xJB!x !B< B!@B!Cڶmt///DGG_kRmܸ ZF`` &M/F,\P*8q"_kGyJXhL&́ ///t}cnܸ1O쓜!}v&Hا~Ν;/^̔J%j[kqƱ/9s8q=CsLrm͛Xbb"KKKc?ba1fXXdd$YZZKLLdl5^ [x1;wOD"a׿6اGelqO듒̙Î9222޽{Yzz:fӦMLV;wӧO'| Ydd$KJJbIII,22=rĦMN>vj5ƵIJJb"7`bw36l_~R֭[6Oʕ+Ν;?λ7^nwѣG;v .d6dͻF{rk(t3fݻ7[|y+Q(((`؁cXm߾kÄB!۳gcO(w}d2+//g1l2ֻwo{lС֧G,115 =O^}U6rzl66m3 LѰ>1عs/d]p1ضmۘFakqFl6cSܸqlڴiCg}wlʔ)쩧by^ vr/G2uOHQ ؍L&bرcǎERRR+U(//RSSa6yHYP͸q`4arrS7ǎlv>_zG>駟'x§~ʝ@^^^e2FF!Cpm Fk3j(d2͸qp dffrm\۝#G~åK'OġC0a'7^ZKyy9|||Pg"XV!//1ưtR9}:z D(b>͝) /_3f qF{!@1t-Y`N:C5ڶz0DŽe@k ׮]ŋ//&=fCLL x @TTΞ=?g5v'M};aǎߢ_~8qbcc~ڞwz~w#ٌiӦfa۶ms'5t#DN%85V,\?ۇ;rǃa2PZZk_Yf6w>MMMEAA!!q > A߾}ylg#??څ F>yW|rL6 wuf͚%Kpƞ'7^$ٌS"##}BGJFbb"xbb"JwuscX`vڅ]tᝏD"=knn.Μ9=aprm ͵_ D"q>}qi8q̙3=OFTҥKt{5L8pOqQ͑#GP^^ksA^"""ktt:"+}R;=SN._{ߟw4N4!MW]?gΝcLT־f4 ۿ?>t:f޼ycǎl޽,--=.K<,--ݻueɓ%Ksα?ޒ'اW3y}rQ&믿._̾P(_͵ٴih4l׮]l.K~ߟ%''dv]w[ 6}tvik.+oqa&ئM٦MZ O?¸20vblٲe\' {뭷ǹM'fM8uؑ8q3h4>!@7p&J)m _|F׳ 0???&?̲yb=Ϗ-XW1ϢT*e?twӺ'?"##L&c{f| fck֬aL&{w>}צ͜9jV̙3Yii)ͩS=d2 fqqq\ij?իH$wlΝPQQ/^:w̼X׮]ʕ+y{۷Ϗ~1^ߔ{}Q}>!FX߄B!]9B!@B!C !BB!PH!a($B0B!x !w}݇־ c s΅N8ڷb̙I&mBܐoBZӞ={_bڵ+Z-33]t1`;Oq@BHgZ! 6?qFf_hZn! @w-Ze˖gff:C ~!믿"** r< {nޘ>}:t:- ,XcժU*Ʉe˖!,, JC K/ L,z <2 !!!X|9, {t…Ά@ @DDD}sN2 x7y#""~z̘1* xxm1w\<N<ɝÀ]vL&c {ȑ#zqu]tDEEA 㞯v h4bѢE F|o( >/^ڜ gϞE``ӽ`„ 4hN<?96l"]n:v\^0T[jj*NiӦӈի_E+V`ɒ%\2C!//HMMFzz:{ܹ tR~P(ɓaGݻصkXlv܉ iii޽;ƍ{Xr%|M;v b>,wn̙ر#RRR˗(%!0B5j9r$ؠAثc,##`ǏΗ2l߾}1{rm6n+Wp^|E6n8{Ӈl6ثc,== û|X1_|'N4z{>T*fZc,<<̘13wW^a}gǏy'ٟ'cfצ[n?f1f&HXAAASPPӧO3\{1O?}QcZI$7pM& e[la7_cj_6xG#x AAA-]'(( ]v{ݡCB p6 /_jEZZcٳ'T*q^S*:=C]ϟǰax5bhZ\~xy1wlĈ=~چ "jZ+##\С:W\3еkWx{{s)&?Õ+W`6y!H0x`א .]GƦMxNi;h!nN p)ּ$8厵d}?=z@$!** V޽;#88Yӷo_$%%񂵤$j5:KJJBϞ=yAݻ7`˃X,vzJXj|A6RxuuRfǎC>}={bɒ%HHH)S_4G !ĉ\.СCi&;wĪUZ׮]ҥKqE|wxxbb̙={6vڅ `͈o̟?׮]… q?5ktfy饗oat+j ZNIDATxy>-[ҥK?p5zh 6 &M¯L$%%aժU ?>ҥKym!˱g磼:J+`Ϟ=8w^xt:T*>CF ann֭[HIIADD ,+Rڶ+WDa޼yt:vލ>}zzzprr¼y󐛛P7oD޽a``߿3gYYYJF ?wwwAOOvvv+=FQQРA[npRn?^^^033.1sL%9s6l('j˘ cHޞ̙CG5k֐Paa!]vжmy&7\.'OOOҢ/"##iժUB}¾dmmMvvv}v:t=_-KNN&p\###jݺ5I}ihhPPPP.++ZlIh":r۷f̘AǏ| GgϞgҝ;wҶۓcM77>L[l!DB^^^ u,^֮]KEGGӖ-[aÆԫW/r>>>CvvvtR ҢVӧOI,ٳKt;w7=}cH$9sPdd$YI*PĉN8! mmmjӦ i4rH"">}/ {s8d-VΚ5KaΝ; رCX׳gOrvvV(7eJMD$$S_}Bݻw ۓH$W* TJDTvI666J ?ғ'OhѢEʍG[]a[۵kG]vUXi&@7n "{sR ?G%Hyyy믿&\1 ;` ÇN8!1c^3gO?@CJP?#رc [lm*5jp2ۚcǎaСGqq߿?q9ÇѴiSՌH~:99RǕ5j4hMMMhkkgϞxc|F ,cǎELL uر#Zjpvv&N~woQE???Vx1w@ `ff a ??ݻXd qE߿yyQe*5zhbYʸ8\xcǎ8::ѣ077ǴiGGG|7]z>G)}ִ6cp/`iii㭡iӦ?V^M6wh֬P xBHDHKKCǎ[V[J* 455|aÆCDGG WaRYJ`dBWWBݻ{dt֯_3g#G,c9H.'Ƙ2;`Ν {Aqq1֏?:::=z4ﯰw;v(߷orssn޼k׮)ahhX8qի\6mڠCJ҄_~HLLnME,PVD"BJmݺ}^>W?3(;>ġCc :eDΝ+ݒ}*ҴiS8::b((((*cL_d~hii7oė_~mb 匍1fl޼ROOO̝;YYYpssx{{+ AKKKرQQQXr%m7|nݺ{2e ;w~3gb}J3f ;;;&sss0`ݺuK(sQrqq!Xk"m{q{ CCC ]xQXCOק˗z>]~IOOLMMiʔ)S?!dkksٳ4tP'XLfffԳgO:x`g}F椫K]tgϒ}Kݯ_?222"XLJϟOVVVQf#""RQ{MAAAƣGP^*ga͸~ϝՄk׮[~Wb޽QuSco3.Ν;Dlڴ &MĉScc92&\]]bɒ%rL4lc󂆆1N0c읓" %S@Nc+w.^XiSW|1cL01cj@c15Sgܴike˖Xnw^n}/DRRtR :Tؾ~lݺ+W\f]D#""2dH,C SB1cn#"dggJmG ݻ1sLlڴ nnnغu+뇸8)?{,FŋcСqit 777|G0aB_n+%p>mc1Ƙݿ666nJԉN ;wFvyfa ˗+1bpaa]߾}abb]v)-+׮]qEXZZV `ff&q*͗YEEED>}]u88&8&8&8&1YYYųg`dd樄ʯ"66SXߧOĔٳg1k,uXn]s|ذa4hPUCTF@}}}HRCDDDD:CRycd2XXX(@ZZZU|yf͚]bU*_PPa9++ @OQQQ]j޷CDDDD:CkyTz9 ' 3qq\r,_J###_z#**x(((((Rx<\MP9'Ճջt|4hPe9~8`ll?D1{la>}[QQQPKex(((((RxSg*Outtо}{DEE) UYWWWDEE)<]Vֵnk׮Wbb!ɪ6d҂L&S./x((k1ԄVxΨ)u1QNPY'0{lx{{CpuuEHHݻɓ'ƌkkkG3УG\_=zO| ݻ\=|2;;J'Bҥf"B p:jeeu1&c:1XhRSSѪU+:t{)ok׮_`|pttݻ1gƎ+,97r9 +++TI.#''N\P52"Baa!=zd4iDmbRu"Sbԩen+yaÆaذa __j&D,,,\.+u(,,.hQ===hkkݻBc.P7;.1Tcu31cNYKIIH$իW_:qpptHR^]XXKAAAeNʹa@V' j›J(ߖ)⤍1L'^J371cudHw _dJ ,aѢEhݺ>۷… \hlll 쌈}nݺ]BWW-[T][naH$7?~,lXr%7n X ;;;,]]\\ yN```cccݻBڷo]]]4j(..Pr  H$,W$)) $ :v숣G*IOOаaCܹS۷o 4@V5Silr H#GC8uT15k֠u000-N**#G) СC?!Ccwwwܽ{f͂H$R)&&=z􀞞lmm1}t ۫rnc5X`K7ߪnɻ+$-//(//ߕr9QAN^,zdyYUާܗ\^ճgOH$4c uر)$$߿OtkH$QRRYR)ڵnݺEMDDLƆKqqq4~x244Ǐ+r ?dffFͣx|2yxxP^v ѝ;wԩSm6""pGRjj*eddPQQNwܡ8 wQDDIR $$ ""t@Jիe~:%&&_|A1GZtuڕhڵDD$ɨUVN'O'N JP8qP6m(22ܹC?4&DDk׮ǏMǎf͚є)Sddd$,R۶m>?ԨQ#޽;:un߾Mw輦dtIڴiS1 ZhRjj*]~$ ]̙3BU>7e)ҁP%ǯ8&\CT.8| N rJ>̵*ȩٳ'999qܹDD%?/Ϝ9݅e+++ZtB;vSѿ Ȋ+EEEdccC+WT(S.X{=d>'@YYY$e/GT2޽;-[LaO?DrUhт֯_ODD Ν;'l'BqԤwӧOI&ÇV V%&/۳g ˕%}~nJt,L&E/Ƙ^)i'*;uihhP^^^MY8{8&T}:"""{n,XQQQҥ r9(3:̙3GUиqcaذa(,,l4Ŋʘ}br]'OŋajjӧOEEE܎??zzzpBDGGrL4 ӧOWfgg''^Wfrc/pp:P ZBǖU'I$t*"mYIy$p9&M@SSPb9R&JaeeӧOG111ԩRe 2Ӯ];ݻQޤIرc?~}d26`puu?.]]vHHH@ƍˍvuԩSСC999HII;99.]═gϞ eZh{ÇH$gV RҥK(..իD}Ϟ=UnSU>?mڴw}'Oyٳ)7@֮];ܼyVsm% {~H꫶]j@Vc٘4i._ y`prr9sFa9s 00pvvFhh(^crƍhҤ vZ<}ƍ+=SNŶm0j(^zs~l۶ ;w.777v abbRa\7n "_~%rYf۷/&Lhiia̙ W}4k  \._|Q)UR,&(..3g`˖-jWe?˖-Ð!C|rXZZʕ+BΝѨQ#cРAe(9o'Oȑ#!Q^=̝;]ti0a >>QQQX~} c~Ė,44U,'rR =^zISNɓ'T*%7oB-ZP_&Qpp0Y[[6mۖ>,l/?SΝIGGرcJeJ;md2t 2IOO7oN3g%hɒ%dooOdggaa۶mdkkKԳgOJKK!C%落=-\P!BOOTJ:uaqƤE699zEzzzdkkK6l={Ҍ324`dggG?R֭PӦM)""ڝ@*i'O*_YL֬YCG?*UY'>?DD)))T*%}}}С?}B IDATd2]v={ڴiCb^pyxxD"!jӦBq'cH%H$Za_cQ‘;6q'""Q*ʂ233:'#99 6|r9 Jk}2ywww8;;W8Sy4ifϞ]mHHH@qm4nX^5&)))hذ!\VOV9yUTTC֮EE ^V?vo/[]-`Vm駟; T [[1Ƙ <,wx.ĪmEfo X!!!>*uVl޼bвeKH$2_5kIJemC~j 1y`kO[;`jNT2%/N V7@xxM:tPÝXXXJ&OÇMOOo2|mb^S`OysULqX Wu`jjZ Ɍ1VkN,KT.@cմ,I ]^^gpcsg@q`d |`^խb/1c(8<crHJ] c1^ϓd` :?@U2)Nc1n'Y\T*V N͙www̜9Sx#`ll\af6moE"8X"+.嗏K?S%8dJߏŋWlJJ D"^ٳg6m,-- ''':tz/_H&*NSSi3.?#kնUfJjb Bxxx{ ߿CCC/^DHHڴi}BGGG`W| ;Mt !U*VM|)y-[qvvv 6lHwww8pnnnGnжm[c`ضm[[f Zn bԩQ(;;;cСPgŊ!_6bȐ!9R)&MB;1{lԫWLL8Qõkׄ0x`XXX@"cǎ8zhbt {Ӄ&N۷e˖ŰGGGcر̄H$H$BPP$7 000@Νfʹa!r9pzmɕt%01'y_yy*_uZz5:t+W`ԩ2e nݺpѣHMM+M j ˖-L&S{ڴi0`jKCC~-/8~8ϟǸq0uT\zz’%KسgtR\t شiSq1ĉصkP~Μ9[0`СCEvлwoB߾}q*Xep4 9c`Q^JweuWI(33Si[^^Q^^.0ZWnanWϞ=iƌDDdooO|M.9m޼ ]rEf͚X,qѥKh׮]djjJB]vQVxܗd2z)dr۽g233?c۷B#FJ'OV(ӹsgj۶myRn1޼y3I$={$ggg;FR;::֭[=^-hDT[[[ZfQHHPNN? JKK#""+++/^cO";wH$(ݻ7͟?vn^EU>'TXXHB.(4mM(%ZTR\^a~ *y" 4@zzzr#$$۷ȑ#_`cƌرctuu_]'Nahh1c ##x*rUTm۶W?''uAaX Dx%''#)) hƐH$uVǣm۶000ֹA.#!!x!z]nŸ,/_iӦ ?[Mĝ1B_$އt{p'ZW\.Gvv6 ii "r},-- MMMaPXXX}d8y$6l؀}_v]'OŋajjӧOEEEڷ_/0ZZZ*=@U;g9rVBƍaÆ)<_X"RhmӫsQE.CSSJP"mcaρg)Yn Ы޳ڬD"kW^%?ZM*}nnn!˅'&&:::ݻ7nܸرcѼys̝;.]b^ZϞ= eZhs){y ΝØ1c-Sk׮!//OHΝ;Dri׮Ҡ2˜:u :t(gSRRܮ-Z~@nn9shڴ) cǎW/qcsLtt޽ܶUvnc*HD@ {+COOL)S3f 11-[iӦ ѪU+ЪUJb_7~'lٲE ذa"""̘1۷oۑ@ܼyZ1(,,pa߿߇+ #G %%111X`.]hܸ1ߏWڵk5jTW^_4zhN8O?ްP2ի۸|2֯_j1vpp@NN;Ǐhڴ)F1c`HNNŋrJaȪƘ BK?s6w'hiio֭[aeeKEdd$.^6m`1c͛W#uvvƚ5krJj ;wt}֯_gggDFFb eF bܹh߾=޽)ST-{F&MУG >^^^p(D8tzqơiӦ9r$RRRlڵ011A׮]OOOk׮Ǒ#GtÆ C޽a֭[M6e˖8pS*1ڵ+&O#F~ꫯ3f >34k  akk j1V{DbhD~ f݀I'nn{CD㼲,!33RTa[~~>ѰaW ˑTZoז___<{L%ӯ՘R]~":tWzW]qLeE`{dL/w)~Ż{vc1V73@b)DCUpꤝ;wbҤIenszjY>\+m`''C3= m&n%:iРAܹ,ˑDX\kzj۬cuVnt w)ꉾ&mU:²zqZ4{s dh@#>Su)i6mn߾=N:Ua}EhѢ߿WD"ҕ'OO?Ef͠;;;L>]Ƅ1{g$c(UHHwލ3g/+Wн{wׯ)Ξ=#F׮]7e#77nnnXbEu<|>ĪUp !""~~~o=2c*Q "``b4`R-c*T'nY~~~?~<`ݺu8r6oެ4Xv̟?0|XnvrgNhժ',;::bҥOP\\ -:cե{ -c1e Tt^>}(ӧbbbٳJ===-_Uqcw} =NW?~ L&~PiiieVUŋ;@XP2hQQB٢"ry*U:>wi㡌c.D.PTTToBwQ읏Iq4@r(@ް'd2;2{-RGBDJ^|E0`h[|9GFFB___a4hR ;;ݻmɓhݺuʼ  _T,]SϽ&c2p@nݺG/#11ӦMÍ7ФI:uuoJ]"//'ODqqcUbL cУGM1ݎ?@6x {ñxT:Sy#** CGEEaeꊨ(̚5KX]VYYYX,+reBG# J֕޾*]R~*׍GUS?" 8e)**җD}7  V SwHTDmRw&&rpbpjUɲu{>Zƶժ杉GH={6;l߾5kݻɓ'ƌ#3f 22+Wĭ[rJ=z3g,!-- 2}fffX`-*mpB%?‹- b1ϭ[еkWe˖(x- 0Ǐr+WDƍ!aggKt@$N:pssݻw:7ohԨxCB$ bרQ#D" ((ؾ};5jX "BAAOsss[nxP_tt4LLLp@OOqa899A*?2})H(cll0a{ll,-ZH20b V!C`尲BӦM>S 9OCM:M15Duƍޞttt]v zI>>> Wj֬ikkSi߾} CCC +00N8Qv\6gffTږGqqq'[PTWv^=c+>ryN H$4c uر)$$߿OtkH$QRRYR)ڵnݺEMDDLƆKqqq4~x244Ǐ+r ?dffFͣx|2yxxP^v ѝ;wԩSm6""pGRjj*eddPQQNwܡ8 wQDDIR $$ ""t@JtQ@.\T*..@200 OOO|2]vr9M>СCtM! "cǎҥ >}._L7={R>}tI233+V/322P""JMM-[g}F]\jҤ 7_Nqqq4j(j֬I$nܸQae2=}d2Yom(뻠6ҁP%ǯޙ}ݔ(PJĒT;j[]pSbԩen{PaÆaذa __r+\ը yE2XxV q|($$U:tTTll,N:T DD͛ݻ􄗗*sѢE$ &LAfoo/$難H!ԩRڴi#BW.==mפXܹsB|MMM$\֭9cT~& rAI#`qNXՙ+@O[q}:"""{n,XQQQҥ r9(Wӽ9 KNW%/>\E"FDJW_eL-\cΝJ^L_ cj+x h}x`gJ8E"ʷbr9u4ތΝSZnҤ0BCC!1rHa(T +++>}=zAN,-S\\Xٞva޽ppp(jP&Mcǎ S tbχ+~gtڵCBB7n\nHƍCGGOƨQ%ҥK ބ}޾}kwwʕ0< nV'Lٳ1i$\|ׯիǏ;s¾sA`` P\zUJƍѤI899aڵxp+eSNŶm0j(^zs~l۶ ;w.777#hhhq,Y'cX L9svvvꫯs1Sٹa*ܹs_iHѣG믿^1gbT$&}[}Sն8de3f ЩS'hjjO?ĉM4A׮]Ν;+;}tdee>Czz:ZhI& VX+Wʕ+ptt_ԫWXYY!""K,' `oo} WHKhiia…x!,--bѢEXp!wݻw֭[KKK zzzǢEW_A[[͛7W¸zj̞=۶m&kŊFvv6:t#GXYի1vXVVVo[ɓ;w.>dgg{+@F @6#c\j+;$++ FFF,s&d4l IDATʂT*[pvvƺu-CDh޼9&M0;JMIHH@qm4nX8&bL^uСC߿?~ފ daȡފx԰~_dՖ~ <رck'O`޽JLcjDV b֗,v> VX5qȪCHHXl޼̩겖-[*$[bѵܢݻw-Z(w{\\jE1YqeW @S=ʱ SR/zO ߤC;$E-FzemgHɒ/ 7-U c5^M(VC0T@.NN,HX9e c1 ϟᓁ?E'Uj'1Xm{pxvyULpc"v b +LlS NcP > dzƪmSK2coڣD`Q< ,ޥ맀H15U7gu;fΜfB*B$ٳgn %%"¡\*$$f|)kc֫$4|ܦsT@dXxqV<{ ӦM%tuuCY!j4 qn8ubbb ##kO]̝;<ĉ\{MŅޱ?<. of؆]vE24-2T\ ^K-K[w%T})M4MMQ\ \aLL! p.30p=%?«#$y X*顏ONpsscݺuԩS+W`ggWlۣGtR53Mqqq4jn#|%$$hxY&Gh4YtOWap7STPɯ]4@_f֬Y >;;;֭ҥK B $$۷oqFڴiO=E^+33lٲ+7c ֭ZQF?ް.??zڵkcccCV O1ٳgP(P(̘1go߾"|appp`ԨQ:u;VqvvW^!33paÆѫW/f͚;{oD:uꫯ˟I֭&88ȓ[Jܸq#/5XMP^= Em.]~ ((+++իgB`ɒ%>:I.v^\ LɟdRd !eg_N΃m>-[rqƌѣ?8r;w$11yf;v,3k,Zmc;{gy[9sɹsXzuP<Ț5k8y$k׮?֭[=$&&2iҤ{5jEO?ߟcǎM׮]QGgΝ71wٷo93f̠{ԨQ_0¸rJߏ7|7xcǎOЫW/nݺU}Ν;{Hyzz}v^z%^{5Μ9Cdd$+V?,rӧӳgON:K}T贰{69fs%߆]$#yDNZ=>69ں?쳌3ɓ'3gك8;;aŋڵuVΟ?رc)((`ڴiY=Z&$$3CLСC̙3y뭷>}ᘃ $e݄ n~p2̭$݅Ly Urr=ttRT*AAA\~O?iӦqƏώ;*DDDP^=tBHH ‚;oW(t{SfMQTFD~~>ǎ#99FDZ-cɹs爎&::I&h"݋NCRqر"`kkP['zK we)|MRY쒿F)1u:})I}#IՆk$ pn{ccG&I%GHPRNC'p6iӆoNg?665kbaaO?ͩS'O6$uj=zн{w O}:Ceƌܸqp^~eߊpB4h`0%%p^V?9r+VuMF(JN<ɩSl_z |2ܺJs!<,,U*;$U fff̛7HjժEϞ=dǎ=zf͚k1~xLRc;::l2ڴiCٷo6m2|r oAÆ ѣ+nݚ0+|I5۷o<׏~ kG}L`` 6lO:Yz5[niӦ|-s7]ta˖-DGGOUg#Ik`YG}g_BVOtakTc888}usIHOOޤ/?*?}R)~<,F֭[ygeO4m [_|6?Q?#.%`I$IKHP@h&(USLi,I?c5k]c֭ÓOz$j'xi>ɟT@hٲeg> aaa 0u,#Irǡ pc԰$P6jI4qrraHDK˭FC¸qIR9 $I$t $/^ ]*;J$Iph|\Z%Xd(I$Uo9q Qly1R%@I$J]_%,* ;KUL%IG'VöɠǺK$=2$IM6 _`vYu^Z—YZ*&$$ &; HJJSNhpسg ԇ>֌3pwwGPqƻIRs+ݨ{{?B Ovd(~zfΜYm/]B(V`ylGGGlllh޼9_aFa4mjժŐ!C~znj3h޼3g9q)9{,$&&ҭ[$9 "ۣH>M=AQ 0FKңd2?-2<,=((s(7n%7fÆ E֯_.]RbGxx8...УG^ZU999aggǘ:u*ɓ&&&w}֯_Oll,=z(S(8hРnnn%nhqT+..={ၥemeQFd tOo&»#$&`͚5\,[L9sF?^؈˗/CJfgϞffff_~1lj*{e˖ @?~qDڵEtt:tTq @[#Μ9#rrrJ EiZ"Zm۷ǏB%>C*lmm4l ׾}700P;w]<1vX!,-- ۧQF WWWagg':t N8!bb[|=˫C5ŋE=6mB={\XXX1ydhqㅣpss"33S 6Lڊzꉭ[3Bwزeh֬AAAsBӧ"͙3Gxyy/%ꫯ 6 .4Xvh߾_}UΥna狍7|ѥ]BLͮ}r3r$'xBiSL)q]iҥxm[%ug*Ś5k m׮]JRl۶Tq?hD~nAfnt:]ΧН Xp8={P*ٳB;wDq֭btbΝZر㮯- ??S)'.^(n*V^m8f6m?/=*bcco!ŭ[Dvvx7D&MDbbHLLܰŅQF0m4/^ɓ'y'Kԩ:ŋӤI6lar˿ϱ3ggѧ1X>>>9sHjn„ m$$t3! ċS=cG&I& ͛7jiwww'))}h‚5j8yyy}OwhBt>jiaM6-Wslll!33]v1qD )Fat:,X`8ΐ!Cҥ 6K.tЁ={ٹȱrrrp:!!9{-Zi;s O>a{`233IHHnݺNP쌿0NJJo[je85jаaCΜ9ss;nܸ+W1b!Y(((}gSṖKhPXY~6U3o<eNtvpG_T>SYtrSwRQu]Qa/{gټ{w؁u‘L ~Cs3Q䕾o '==NV5$B!==LlS 777իɓ'iѢaF!44K.yfחǏsNKhh(!!!\l<<<4hT*i4 Eff& (Ę @FFFmZ*) t:-''6wA˖-ġRwddd;|rrrطoF#::h8fx5*99e;]աOG6h` QyZ=tqqARuKNN.6Wぶ1III)2 L֭Kfĉt<==ܹd\\-VVV %}fffXXX`ooRʪT*,--I_;mv\VkN0l0.]?l 7{{{ СCѣ`>. g2/;c477/Z.֬Y3֯__9y$vvvT*c㗤ӧOӤI\r85k=kƍE?Q*brg[᱒ZJЏ>HW$cMnn.jvڕai4ԩ !P[2z Q]_բOǵk,9"*z+f]rU2?Z AAADGGӻwoC{tt4={,q`cǎ&n% ܜhýS|'%ciiYby ssb4ZBR4 }ٰ,?nI1yxxVٱcu fϞM˖-_>lݺŋT*)((`İe$''2̙35kM6ၓ;w&88>}ӰaC_֭[ի-[LJxN;s2~xƍǹs1c'N̬w:~I ʔ)SpqqO>(J:vHxx8ׯ۶mc۶md6c ^{5֭yyyo0q"g$)RDP9(+L^&l/7zEυ[9w*'edseZ tQ^  B^S&L8_~-[ҥKIHH ,, VvmfϞ i׮1={dӦMܹym ϝ%{xx၃#F7'''&MDӦMygqT^fff̛7iӦѶm[CVVcƌիjXz5իKw0޽lmm9<*@lbbߺu+SNeܸqڵkg ۷/ׯC|r V.]vmnʛoI@@NNN10"}G?ƍQF,ZYf1sLˤIXtȑ#O?孷ƆMVۧH&9X2< tzǂJɪ3Xyz%KO| -&ĥ݄{[pEb޽u۷7d+ߋ  sss'/<+#ƍ'Zݻw 4E?}R),SAN~/5%^PKJk2zW'>hm[_~ ]+{d(Sh̘1R#wڳgO~ѯ_oذab̟?AB$I O>W`[bɴhuZ~r=Kuދp:{u6v I%W_-qO~իK\K/dɒG$~\;_n& G_^G*=!#"& pS<^0WA&RѣGZjU:S!gҤI%3Yd4a(I+G |$+'Ž#hj3d(Uvvv0f$Iž`ߧZz85vd=Ħ2/f{~edp ghK IDATO&LdI'2o@.$㹖yC- z, w*L+H%lC]I'mJ¯25!999j95%}R)@t4W?gikܸb|=KO.vm;7fB  6rtRi) j֬h4۷vKG(LO_iuZTq4H"/iuZoeቅ\˼S˯W' y5 #RK@RQPPI|d': l9`_:-/] w޲_%$@I$84ӛJq㒊8|c$`gnhWP$IznwC $@Nh2)̏Ϟ{}-A1WP$Iz  / Pv-N,⇸ zbthY˯ $Ihh `pp~86jX^Jn ˏ/ght:.kUA2$I*^Fꗟ<`lY,vbYhń Y˯* $IT듿d I/cGU=95bBZjm褊&@I$btph.>5ؑUk%sV:ftM&d(I$0b/}ƍB~"b"? XK:{u_5"@I$|]?/zT'b|ݫ0cFPzd(I$! i2hKɷVscGVmO9ϼy*J$I/?'> q㪦g^gቅjaa$ @I$ܼ߽ g@CS=vm\V߸qsg$S"@I$NoMᐟ6n9x?e쨪,MάbdiYO7J$I ^mW`'//>Jbcɥν S/f0J>F(LL%Iv WLJ~<*:Nj?W׮.t\ tl<~{l@-{I˛m$IJ/nD[`߳Ǝ(77f.)]0 zc܏h+DȵV$ @I$t:) h/cȪ'slNZ~am^ lg_/l<72p䕶Ԫd(I$[-X? ~/ >s+UMO9ϼsew-?AhO-*-GC`N&$I@ v QUY:ckV_Pj=z*h+:򄏓qJ$IymKrغ3**;y6ֹ;c;hȤ;P$:K  O,Qb-?!f pkV!5E2$IbW 75~ 1vTU҅ =>PBiF?G92&`O oS0S*X!jk{ $IRuYA%8z7*zuX@'t(JzbtJ[O8 vkX)ғWףN9Yݔ:trz6 111xyy=pP$IRLO+04n\ULJn N-c͟k -+m-O%pbBVuնn0IT"""ppcg3VPI$IaX `n=A~ƎJd*V^aLh1f͌]p*qV6vf kMhl,Tt ^ͭTۆ) I$ 򗅰}ZpikCcGVeܭi]uwѲ"q=-l[%(u!NW ##zJ?̽h"|||"((s(7n%7fÆ E !1cjBVӧlKϞ=qqqޞ6mڰ{R,Idry<~듿a.б\,yFґ|eW$k2a-ZD6m[n9su 83gһwo6l8pZ鋖~'|X{>N:q9xxصkjwN\\\I$S}ZiTrpZ~aXJY/-G^⫃[ftH}\JTf|ʕ㏆z GGGZn˗x9#F`ȑ4jԈ<==YxqGDDЩS'~mxy駉L:>}ʕ+o͛\p)SЬY34hG}DvvvBIJj)d;)rp"CXbSb5 X钿[ypYI1gg,i9y3_TYfÇ`"""زe :ׯ/9vSL)޹sg:T>_/֥KCORR;w6}:tW_}ggg5jĪUhѢDFFNPPPG^^a9=]?]ѠhJ}ΥQx>ne%8'>49OA{=6}qm/e9v/`= mciLI|qk^%@ hs%}=6qGTNFW&tV)r lܸ~+ЦMBBBX7oD^ݝIJJ-iJBAtt4={R;۶mѱ䢜g+־c+QtttQkqI@Ϛ} e{)mR9gN@ P "VqLtPbɃf.|Mɯ7h~DVй&5RQ\a#Yѕ)֭[ԭ[;vF㬬)S w$+獳^qsscj wѣGYf|8qa9==OOO:w}>FMN07\*dWD珨"/a킶R|}UnJsWjuӁc+e- əDSIhu'kJds3Rx:+SةS'FI`` }oo: *h_rrrBܾpGRRRDڵ-[bH-ZDtt4+W,vI--K577_j|(/̾VͪU$[gfdj+s-X;$"yse\G_vBѰb.y/epBqQQQ8;;p1^|:AAAņ⣣iݺu~ǎ}||(M~~>{5lS8TRN{s$I2DX/x w$Rh[], N, SKY—t_LB #Vy>ĝڰr<`O2:::`b%W'N_e˖tR `Ȑ!Ԯ]ٳg0~xڵkLϞ=ٴi;w 5k 4A̚5 kkk 5j0tPMZfٲeF4%ILV>X7n=\{;JI't /jU<<  w2op^RbL/ =d*J_Y&$[bf"~_ zԴ-^ThYs{HhgeFhkoBPJE.J$I(? ''{+H榲2|T:h֔aO4@r5ZI rErp`S>셽U8ti$ITnwCYP:\>˷Jҽ%&ر1[n5rW_.'Ү>/>ትFH$?p[w;JAհ:"V [M6(J|VH[!TK3#*;J$|~~]_n }$Z~ulNWߍ<<ׇ/ cC|Ѽq $I)Hk闟J~Lߋ27f. o_bZ{/ OjTӞq|J)/KK~H$ۅ5 rn^ ;*߉8of[s[BCyK[Y,ǺcWhmHxG_:|J _͒%Kxyy={,%I&~{? ЗxmLԋ̍ˮ+}-^`dӑ԰aJ_,Ǧ>Dx,?•)\x1ӦMc„ |h $Id݄ Nr8t 沀$e%BC-?BI=0Z w_`$߉_HCWu𥥷q2%gٲeՋ>޲eK&MTnI$UIWKfjx>^0vT&+57/N}~kѳ#x.`yvahă|iZIUx[ZZAI$UIBc* | IdjX-#GwBb /IT@ZcvFPʔp O4n,?$IM;Qܤ72 F!*6%/1kX!&j2!%3'`RзEJR7|cǒ#G2{lQ$r |2ܺJ3!zU>:c[6X+@ Nb ILLɋOv6r2%[o͠A]6s},$I&@A׆+ cGeRJdDX@3Z~Z BK^|vFP+sQF1j(n޼Nͭ<$I4m [_|6ƍ~whQlmmˍ_6Z~yZ]c8ng`oeFhBxhma%Ky!ITu\@OoR>ԋ;>~\i΋~/VZ~9Z=}IJƂmғu2KI2&nbڴi޽dt:]o.$I*s?W!7 Nw>cLFRVN,bSܦJW/#W_bEnex[jzx]2*2%/qqq1www%IT80G\q~u[-p|k9{K竃Xq0<ԌnKߠXO|ʔ8p`u$I*\_5./ N3LVR- &@sFndŁ>||0/=ja2Yɒt/eJ)X$I*KapL [}m?>Ǝ4: c׳nƷS2+GSsX"I @SK&($VE1eM?Eox/$ILph|\U#3*бvoW۶6tfҵ.b8bXבthfIkU$42:(3'Vё4:vX]B@ՖKp$I&)'6s?ꗛ s>A뇘3Z~6{7Z~eh6~>#3} ,G,/.uQmތ-&O9têrʔ< 9 D%w}KnCPh~'(RoXa i<ĤkΒ}v:֡+:%G%mf?m%m]9nhW0{uR ԫǏaÆ$Ii ?Nm8_hȌZ~/L߱),9_}޵c;_UBrbbH]Em¹*د/mۢ07ʬL `˖-rL%Ia$8?r.{ XWQ$6j=_y6k]m@JG@-ƄԧC64ɤmDZz/]2[x{د/={bj2%?7|MҬYr N$n/(h:(Mw2CEIM?䛳jukl-?!Lf JAKg-3]= B!s^R֓o=W@am}8:0PR)8p Ç7) 9 D&83}>#g?LV'G v_lb:fJ^|.[]x9UE^\QI۴ [vu` bץ+*;yʘʔǗw$ICipx~nk}go7+JI[m&9Z|: \,l,T͈|pDpqVe,2D(rN0qǾ}WψJP˫$I2  n/~ &\Ƥݭ߸q=grN^Me 8i?7vEFjʻO(R7mB{&ž[7TJO7ʪB$I*w7!w0SCа*wB_?LDL†-BLKbogk2C}Ԓ']V۶ENL]TMiDlw 'СC"??WP_Y,Z b>**ƍciiIƍٰaCBf̘AZPՄpbiժjS%I2a7×?kgcLN8#yu竜}sš1~"/44O7yK/*>-jz;n!r" ;8ymۑ8u>S* yxa!gV+eryΡCNyBk׮e„ ,Z6mIn8s u-Ç8p 3gΤwlذpZj'|Ί+xԩٻɤwJIhI{hR X Jѵ"ן;f-"MPD@RP!@*dfҧc!d&伞LJ{3ssHMM ȧz˗3l0$IԩSչ% 8Ka$(׀_k_tuy_Z~M.ObG;P&I?$_8YdB`f E#+8]_m*'t?omwnyk F^Zc=}5b x |Ibccٵk~!jcccb޼y̛7ˆ $X^z%ի `̘1s=ǛoOb˹-%3 3ݞs_fĎS|)yrZ#}Z64ӉlA2(gqqW9cH|'ĈZ9z(m0W`0pQ^|J#F2x]bb"sέ6rHbcc //#FX0x`1c(JwN^^ݺu㭷ޢcǎ7^G[ __ގ@܏=D'(wC%|$q,36m4g>C\?91-l;G3(kyق]v}v mJWp>:L&Smv4gvЁD~~>fJ5yyy<7;ŋ6$h"VXAVx>*!*qO2Pd50y\y-FoeUZ~aaL&J~бSHK$tVp6?ϴiիcժU\t3g0}t= _gرlݺ~9s|r g帻#c3gpBBBBhٲ%o&'N]>k'@qgf3{N&h,cczx=;ٕ yDA~6g`@T".}FMnقꍹ]:V_L(4hoҤIdrssԩ;v찎]t J/y嗙?>lܸZ^Yfhdo&NNNL6r"##ٳg~~b!ASA_Mc%EOڻWՒcݴkդ4{Ҕl<$ϣ3C!RFUL^cSGZU~~Ox\۶cQ!CTi05kfͺ}Ui0a&L) -ZĢEZ[o[ouN~ [#ׁ'P˥ćUj6 {wWKp_:ecHjaa nD<9ڸ8{א!(qpVh*mF;yWl1A$8 ?,;qڵ^u|zS'<~v,%d>5f0"[L:0]BGFG3~?I[VSuƨ(\\\;w.GɫAjb/fCRP֟9gr%GA^G9M۹wZ{ֶ{#0Sdd2Qr8{"M25i҄T[RAf(uK(`rwi#h1f>:W˯r-9=0(xC%rBt!athnZ 6۲ӕ+v.]:~7/]R'+ϱ$\^{5vj XI+\ ztg^daݼ}.+U_m$srRnA}H(aME靰Qk7ڸo&tOLHBnݺP($R{߾}g Pkc0\} в{u[ro! pZ~ɗy9NfpV)+C -F$Qq$o(ڱKiCc|c&5T$tZ`FFFmRI&MpuuIA> 'AUiS&ݫt& sW˯cLhZ~fS{5<.s)Tu#c{!c>1%& ͒@xWO(//iAhda⸓h<o [['fzV5QoEq&ttoLH; Z-< wFV/2{l-Z[oEǎ@AO`߫_^nS`@Aʟ{dzDR/t x=mx$.6-qWSkM]1DN$t}槟~Ggܹ|TTTsN,Bb6¶:y{ 0r-sZ~ݛvgN9a׾ʍ|p@S&sxnD#[᪶oֱ&tmh  :< E):z۷gq2k,h۶-?A}1|( Z=wpZ~%zw0/.R祵j%c"[ߟ2-MqϞo$tڱ`[wСCڴi+O>dtL8MvCۑU%$n;_s>.U?|r{/f eTX&v8a92l$!kK *\\9 ]@łZ}c.Jþ% AǮ aW޽$1'b_faWfa0[3CÈ@G#-fua33i..::uwB <[|"(${ \@EE3gάnڴv=t16L -5ػWVIIsZ~v|G;>jZ~WKp_:[ec{cp7v%ݻ~Gٯ0#'txoL Ƥ >hSڴ3 4I[!)0!7<%x4w0U" ,'MOȭ[{ϱT.a'1~rd$QqԍyBAix8O?ψ(!4Hw~gAC =1c,;vQ&,<9;=!-C5nrdBt~.ng`|1z4Fב#PĐ 6QJa@|H|_<&12cŲ!eMݚد?j8v䙡aDi$͔< #F{>(J BC&@AjIg™k]8DCXlbcxx;}%I?=g'q݃PBxy%6ۼ:A  P5Txjtd^Ql(#ow6˺,쿐y_,˼u8;)yW03x9_*nj7&CAp|"h3a.0i b^SO,N\̕2vs. Ns]*DAmv82I8}m\EUN0ߘh< pD(B;%x.0khɮ]ul; ^-X1~Lfs\((ʼnGAu&oE }Z]OL4ơn܎=I ^^8 $Bl.xGZJAEJix39Y*f:GU扁֯>n"#%t$$ {Hpv:&X:lbbuD(m>&7+)(/_^eׅ]iÒKڤkTob hӃpxbCf捄Dޏ!^u 5c(o"gإ+$7^:bN2':=ZUt<HQh̋g@@T Ih69#5ڮ 'z<Njh!BR4\Pd]RMZxOH{?$FA|X? +D:KWrKrYD:6K kgt9,~]}=,6 9#m\%?(9#2R$t%$D~V Ʌdj=d7~y[_\=D=P)<kc/E:EM7}mLe8Ĭn[᤬ݯsWJX[`}Z05F+؋!+ ݦMh7okmwAN5J$t$I(dy_vKy;ޏc]/QP;u?eAuލ̢L%.{P*`TJf6f} 't|6.rBGDG;~ ;P.• č:,f1< *‰FIY?%ow c2k3YL>WbP{|vJG$Ib_U>s#58)DChttLi!qPM:bqW$t3$a,Vrj_6gN`0W:[,WX߉P[3`?y{0E~CIӤ1| | -$3Vw2Kg8I!<= ~rMG&'tlFysF3AT x}U7R L.ȃ˜wp"wc71.#@A\E|5J1.o4': ńcLZe+3T$)-x64nXrBh7QQ䄎uSX[R#9iZkr(%"!4Dр5"raD;jxh5Gai2}Ľͯ%Ie|G2%RT14j8$IT$%Cv,EE~}{EBGT IDAT=`2KבymoR7! XjZ-j5"+)n2)L wKz>: dŏGFʨ_b^r_2 kұrT& ))vgxEB#X$3KaCffc.{FkD(Be`dA0~'`sku"jzz9˧?g`HUcx8O lݟ Y,&$Gq7:jƣ_?$IBwejЗ*l]q#~ g4[k"3a`6@H{6.7Y Fw>[$Ibי,v\vD < Y [6c]BGD1"ÑJ-$ڪŅ7) O9LrJ|Mrssر#s=z|\\'==P^yƏo/I/fժUh4"##رcsz"##9qǎ[n%~^$h? b>[_׼_YLƄ/be.M @tdxDk ;(ٺC\ :&Ĉd0sV{mOCAv_?M[zƍ3g+Wd|$%%ѢE*'&&2i$.]ټy3= 227x+VӶm[-[FTTxyU.\ /мysN8Q'W_C+>O}RU-5wؘ[A6d`9& jC5$ 7}TDW_f.-rߘ x;x,(f W2%nr|K̍CWUl=CF~)dl'BxZբБ p 7Z$t8I()Nbs#WB_p;?ܼDf6 =ʋ/X}Ĉ$$$5̝;Rȑ# ##F*/Ҿ{ZAW~TuĻ"BmQ'?)N*ҿ2K˷s(ϱmlxҊ}lzb꼊2Y+OmsW 3Ix+(%t]]) ]7)TUD2^B_ N wF&\q5JG٧S6mkH?#eee}]`ƍQTUF\Ref͚f͚H```Mٳg{Ŕ)SXzuΛ7޺]TTDHH#Fvm1%*#T(E S)R8M{Z#+XRdj<W'>,,5VY> /h˄A(mB߭EF=~o!>;UX,Y%dhNՒQTo3k#|~n_4ğ}*:;;ӳgO+qgر7}M~4p֭[ӬY3]^`0~^u}]-[f}}NN#GdƍL?rqq0Z#q?{r|=|,XLסv}|^NEyD!'ҹIg^bH&}L~ӷ<>_^:モ:NOxo\SUu$I讔[Nՠ/<ǙGOt^Ci(V Hx_K$mz:cFy M0X]ڵ7&ѣpckd )VگT) |P9D0k,f͚u}6a&L EhѢۺ~V$P <i xM/#I[m_ߤXZffיP+mE$ewI\)G`Fu ;Gfou c)GQxc//G=o\;vIlp9BR4\θyk]a@Al*r~'W"F29%9,JXDbn"wfI%:WKX4кKv&6N]HNF ݶmXt:k{߾D%VE$a,VrrO6gu䜭Zٻ(,܍D(wtX pG6BH^"Y*+9e2\T.6⤴J{O.NJf mpq_#0:tqHJ;5koxy;V\XABʋ=̍@ 0Ap7:"oK v4R%&,#hڃʧMce~{,\8wh&,ӉϪ=BhЁZѿHFS5č?`V$ҡhY D(w?S94|Mmvz̺uw=*91&Ra r{Ihʂ1̅3ݼݦ.m;!ѣEB frudȣ|W.) Zyޟfa^KIуD9 ~vH>O<sI"E"lW2` ,F3NJOӚ ,Í 1vXoXGgH\Tl]W7/]T_!m7h4r=z-UAsr2H{Lm>&|+1Zx=Wc@&1[OsJ }Zl\'8~٦Qm'tDF;!{E&eݤ5 no(, @Az\zC_A'k?8ByAYo!<W,ߑccFμ`9HFBǙ3vf?ha:5i#+ECݜjKp{B" vP /I@RPyOɩf#=_{$޼EFe_fĺysW*& ْhcɒ,~FBZ0V5lb_$tԀDNk_N 0D(MQiLJWx ڏɩa~|j R[ /حMp"S[Ns*[~\9ȇe:5fװ%cn,kKx1fH&I.RHV+PYMB=ҽVY؂` "J2EYHM9<j|ZY+X$ ~.~rFteFܝº×$ru⅑x$%*+!L_SzOk+tکxx$DAN5q# 07q6ϟvŷPD(ŅQ8%|W3|.]bwAlHT=yD˱&雵Z4_fLv>}䄎(q y|Ʌdj(/6V楖/_[uûPD(8< oPX{c]:$$5/3EGK+fr,׉mPfZt۶!UTwD|cqn=?*Jdʉ)V.䬤y!cFEfA #$H|v,oGUv֯_raaBJ9m^To L 7 qvrIL~C.OtqJGd2=&n\ͼYfo#iG@kT3  QY̰%89F.e'Jx;|{ɂJɌm5$ 7;$)\?lmw 4|ƌFn:&kdy_jv~, ?"Gkc,Ъg^CKZ~ DL⮶MSU~{|`,ۑ&[:,eehvpZ ˍ C?m*w &j&f}čv4iR$nB&@Ap9aCPra74ZThxWٙVޭX2` ݛvIWt,ݞr^.ՁQ]Xhv.,{Kq1J|b2-7G"`֑,g^Ptr?>8̂pW 8s?W:F$v_)(DPX{׿Zn>O;i(X̍ >KID/R Jb[Ӿ#&IPUBbR 9dčBW\O ۱?@2C0i-ziy+p|X:`)wI7\(-IɓGzeNtln1wRQAwQf-Tk{ix8asgPʆ[n(_.|1)S}~7/5i#ލ#K[!@AIovI0}p$Ib[6^5 E8)x˓<)U5_Fk;\3]ͼ3g]Ь߀0k(;ɓ!55௼@vںWn'g%Am`/$b 4`"{0`\ y{0|yYt<9 ?ο]hHl<ߧ-NO ߣng$ѬYCѮ`׏U7oߔ)NAhߍ͌3d%kL)$?Jf lCfa c&G&ʳ  5} |78JxMB$6̻E/ңVmv|sNgxyig#l,׉-j|;! }=_e{o4 Cj +ɺ{^$U:ƿ! 0:a4X}CJD(u䊜s `wt,%.pa:7̲hۦ+0bw_$^"Gez8.0r#_bѣ:׈eG'$ɓ 0gPatK QY%@A+`m4h/{#xx#[$ _|IoqQ0L=%QKpuY$I|{"eۓZ,%:]U~ 5_Pc'ҵ6}!/PՓ"J.Tg݉vi& P-" A~Ţ,8߮@π=NWKX4кKv&5:L&)\~uixB}w$їIX>M^Y*'%a>č! !@AmA`=+䵼w=f=nNnyΟm4]9 }YAA1ce~{,M9C5aNhTVYݷېfUFMI6>"UbǗsVMݬAmEfA6X,b8+o| xT3Z|v3>:FO'64e,ޖD|epLGFtչdLɾ}YK١CvOt2EwB9KPQbzˬyKAD(VVa/=))?8bAxԨKORaT=m0ܝkk\Tv&4c̔U*pѣ'1ɣ| 0EE(_{?QY @A=e Ƽ˗V\ŧ>$q>/`k,$3iүڟe:Uފ|knقT&'6(}|{h"~?s(z39ր/?~RA@oyM[{: C` vpw˗z 8=@T(o5QW|{2[ә`\Z,J5%< i=[[obpb\%YCys̍<^/,ķ Ti\I{? QK  V_X$ #ZQw.֮T& ַ%7'XJKnقf: rBСO{߾ꑧ$I讔s)d 9i7)bMn珻1QCP]& :tٰe&d$o{Fʌe!eMݚ2| Qweؕʆ_.!I >-Pٰp$I>Lᚵ(ԹeK1q/AED(blKPu['U3sL.<$IK&Y>=٢Mdνmy|`k6(7"IǎQf Ż,'@2 1?jY[$IB{ j茼wn\[f-?-:4"("B%@Aޑsm)tzoߦ @W/w$I|:%%[xoвI2@Ν~3g}?}CP9ֺrU7\RO%uA+WoKǎ{?>鄆+0~x~IXx1VB|@ǎpK.eϞ=ѼysNK/]$PCW.nS~lIΌV@Sy9UR~{iWhQvWѥU4_nDq#|..xikWےdrk_ 'j#]Ѻsyl]Appƍ̙3+W2`>c~hqRL4K2~x6oC=āGfx VXN۶mYlQQQEJJ ?0N>SO=Eii)oV]Hv rpQq-_YɲHIO_L] T|?1,8ʬ!\OM߃QNp G}h"N~5+Ic+7 (nkړwj<A[8Db x |Ibccٵk~!jcccb޼y̛7ˆ $X^z%Xz5_3fp}q}Y٦MRSSEؐ\.y!xeFIGzp|bDCVP@j؏ik6WyIw˦su=@PTF5.tJ fTJ١x:a2*Dn:@{CLS&y`(7%3Yۻ㯙aEYPRp5-nim^KoV7+R[[7Բihj). (|~ex9s7o??Nϡ0fQ>֢|pwaTllTEbbObbHZZOϻtdC)uю9O:ŀ2sLMf}ժU]~?+..o9Zû1'R2*v8v݋y[2l^)nn,HCC45?Տmb"*QG㷣=.&f3a#nǎY7ӥ3Z`??C)0i)=DY:P,jJ\+0W0SP&ʼύ==-GGqqqmwzN̴;h4^h,#AAA=Sٳ'|Ǘ/ȤIiРϋf2o߾2;C<hvh( *@xx7«9dIfnIBz%bbt7y{!;5إ1'k*,C^>x m]׊rɃ9َ`z܇Q>5BoSύ==-G'x@gggbbb%..cbccypժUtdYY!""H\\[,_7|zLZZ={$&&磽RV~y-^]xLꜺh[&/x$un Yxk*o8@^Qă0>WwNz%Թ꼼~|=T#4Zw!?7$&$&)rS I1bm۶ĥO0rHBBB&L[nu]^ 6G'Ndƌ4i҄&M0c 6l`ѣ 67gΜ(,V^Oohuɴi<x9yMc{ٙ @ O^MLXͧ]Qf3EAȧrڼ&M9;DX>\MFK?#xC!:>dee1}tӉf儅YMIIԩ/fʔ)4jԈ%KXX{?RapT<-&sI y?D(;|غݽkW|Gsg4Z5ǪTI$76́5Ӡ]@U6cJ){˟)(㋄|9ŖWT4W~-yyh2pZ7U:CɾxaK? ! I(nyiw[>75/翉"s;;lN~)+-TC_7 jN՞S)EdW[Wч3|85c^⍆-m#BqP}?M\лA3nz+d֖Y"!0)f>.56bX4BmzhH4f2gsy9,'/(MJnwߑ#ףݟlV9Q@Jҕ7Z,BH$7|ue~?x 7kz$o~i"pp^}媋<+8ƁخϔI%,YBe% x FҬUb7R8)B!.C@q:=)Bg󠳝d6pׇVyգsUE|9<76`tU_ɞ=d+V 'a^|ndM#@n}Io!Px*Mn&l(3x=CÎvMwfdαTӶ7gJ)DxռDVmHm')1Y 40S7˭%DU|;]۴w#FݲƷ[Vl2ZƷ/B!jL@qcPPQ.plhy%+O-or,w5g>["J3+ ؑk޳Y}vS#jHөSd/o\\ Cy~W#_ [̓1,l.H(Ltߏ80d.x4;Yp76 yUT']{Z0KM==>BEMux#2Elʊ*lWWa2B!I(궃+->K zx^xj2" {Gs˕,ieV]}5Zy#D̖ o!I(Ꞵu|,˳;n/P7k6Ehp#ofbݡLL±,IM\TDҥd/X)4xzP&eW[lY[ |q' !~$u6 f<w͝wJ5-(5Uݎ4>GXh.Um*Μ!{ї,^:__|G<С6͊99c;PQ~aO% [Ѱ!VI(ꆜqHd.c6^hRÄpB_}P*OyENE 08`Nw֕K&{|~Un99, Gk]h]\.\d7ephiʭ۽vb7zgYyC!D ]JŰ9(/g6:ԺlbaBT`B3e }ƑB>p(…xHphoN֧Qo[;^qhin +z&mhH` !$8~~|n|­Mvʹi"^ ^6RJp,OHz1k$GqBY~FɎzoh\۴A`*ض nlZ' Ӵh?tL-BO)8~u/Bg@kaً޺޼%/WTiNþy[$m|/ )+#Ȟ?-x5GШ彾9ܔg(ȋ4 ]8B8$O }m~-q23%gh0n@o d.-),?Nz^).z-4`t"ݫFn.9K*Z I;> Sl7(B :ѬC ^ݮyBA@+Άee@gQUp$kiiST-q= <aW?s4.$wRԹڜ}x$GYOش398&f0F{}B!n|O<v- Gem s1gJ*Jp:1ecgj.iٽ/,Ѓ1]#` NWؖ&%gXߘѸ<!uu]NCX: GB<$ז G<=vc+'.T> 9s^yT+fv"$a]hH6v4N)Eцd}) ;3z4y[4G^ڌ{}4`I@Y!MK@qmîapйAX'.`)xo{,9Ra٘gx%J糍ɜȲ<4;X(_%(;hI*vtCFp;kgPz :iw'' k`<6Js-ۜ=h^XS IYԵ[v7̳m܍W)XrPdž kʶ?Qe7* [?t{77JV{EgKч^_P#/4,'B܌$mǼuO8txn.e&e 6Pr]@.yxҜ7~L]i*-1K Zr)Sf&9_|A% ,)44(Rc>iPҗDIVB$5WYMs ûB'8[ruSV9}3 kS {ӫA/ '}5>]fTQ>BL(8 gMj;+ڽzЬ>7꫄BG! H\h%e$~AH+Lc/Y;P(^i؇(+ b́3{&hHnk]}?$1 )\bm{QXS1{}A (B8"IELb XT#,duj~:ү%'%e2tNX۸;ھ!: UYIa\ >CZj*&'wNwL~s!E7hԦ>:[B!! Mdߩe)(kɑ[r/A _u0q[}^BqE$fſB.dَ4z6>ʫWJ`!9?tQXS~ߎ'sOVONlЛ@] [Lڜ:ύ><>uT"'"˯ȩ"#0ӝRtaпA=|}q2\lB!$7wd NtoZ_on:Mfkb|<>iT߽F˧=鿬#V2?E[DwiHNB!$7 \ql̓=ql8Ʒ'ّ_$Ҩ;wkĐۂqHvM%B8F7<]B!DQg9soN˖-={6]vҥKy9z(57WJ1m4>crrrС|-[駟`x{_f=:qf0cޖU?]Cr{At펾̝JxrJ)v Hq PLrz7'|Y _=o,&B܀Dd&NȜ9sܹ3}~;III4lЮ}BB<w}7}?6lC[,XMӷo_<Æ ɓX{#FO?]!UþX}GhGvy4o€΁1kz:Q6QH[tVY꡷'yY3BHH}]ƌرc={6+Wdܹ̜9Ӯٳ۷//"/"ׯg|(={6/s .$00b 6mdM͛Gll,Yf*Kٲy6ZǺB3|uONH#8QV%2%|.YXwށnt' z2Bj=,//'11^xf{~򘄄ymg$''A~ ݻw'>>@ǎ">>2|R& tw^ECYZ+Peۅ  E}L9Z| +@W<\p[]&1'1'1'1p{N'gϞ@큁dddTyLFFe۟oUmN8am̙36mUVV1F)-zͩbהW3(44Nh ȶ$-Hf]5qqq݅:GbbObbObbObbˑQ\\\]uw;fJ˾wVWjS/w_|I&Y?ӠA뇧gqgr,~;:2ZjMH Cws2L&qqqW>GbbObbObbObbq #Ng7ꖙi7whl{2]FFAAAն9}4:sL5  ^?4nj44q~ kZf 1'1'1'1HpZٙ縸8:uT1vWZemhiS^^mbccc˖-67o&// !B j}`ҤI1mLJJ O<#G$$$ZG}X;DB!ߥN$<YYYL>tY|9aaa^ԩ/fʔ)4jԈ%KTN<ƍgzժU9K~ikwZ!vԉ`ܸq7}֭vrV{>FԩS:ujm|}}YhvU!V !BK@!B# B!P!H(B`$B!p0 !B8IB!$B!άr#RJm2(..&??^xؓؓؓؓrx{H rOBq n r/2͜:u 45=w~~> 4 55OOkzÞĞĞĞĖ#C)EAAh6ZпY{{{{[G;1^!B& B!M:ujmwBTMѣGI=H<"1'1'1'1%p}lwĘ㏴mۖZnͼyȰ`{61C6;v˦M1 6ԩS?~ڦ]t҅5kp!vņ 8p 1X]h$&ž$uٳg$00f{`` ԫN)ŤIҥ ddd쌏Mۋ5##.>>>8;;_M`` ={tl߾3gsĘ;vsҤIV\O|ώΝ;Mt!h4$//-[Xl޼<6t9V"88pkvcR\\VkYYqĘ\.MrO>ի1Wp=*MD͝O?UIIIjĉ]?~vU|I֭[ӭ_6O< UWV۷oWzrʓ޽{۷իW*`S~jJ%$$u-LoՃ>٣-[<==mظqtj֬Yjj֬Y2 ?BBB,[Lɓ'[1)((P;vP;vPzwՎ;ukҗ;&&I @)gggզM)7ʯ[۔+___bs'NA)WWWƏo3ERJ[NnZ9;;p5w\՘^:bL~' R~٬^}Ue4`PݺuS{i<<<>|ɱi{nյkWe0hTSNNmq޷~5kzRK.{n2Մ TÆ T/_7{L֮][~X)U&}c\ܵk޴1FKB!75yP!H(B`$B!p0 !B8IB!$B!F@!B# ѣ'NnX)xEѰs53j( RBAN!M+V`[HkKWDDDcn6\BH(UVVhjѣG ՛L&z>Fq3GB8=zO3yd|}}1L:պvCsssh4[u֡hXr%[Օ^zɯJ?ooo2eHUyy9'O&$$www:t`. E N8Q彮_c0 ^<"}ꩧHIIA^m̖.]J˖-1 ;^cذaԫW`}6yyy|ڦ3g'o>i׮vbܹ|駼 %==&Xbb"?CeϞ=L:_~ ش{iժ۷o_gT)ŠA`$&&ҦMzMvvGoaҥI&uV֬YVl6eV^Mzz:˖->&OҥKYp!۷oq/۶mɉѣG[ >PnJbb"/_B%p8ݻwW]t֮];+JNNVڱcuNNڵkRJ]Vj63gT:zu?os͛+lϫ͛+:rh4*--ͦ{V/RJ+@ܹK5kZ|WTJ)G]<Æ S}sϩ-ZX?شy߮Rj͚5SڴiԨ裏RJJ׫'33SjϞ=J_J)K)Taa////WꭷRJU=_JJJRJyxx \BOFpPZDff_:O`` nnnDFFl;vDX?ra*++پ};J)6mJz_ׯylwڿ?6ܹ3_|ee???JNN0ׯosG2l0"##>MII==zdsz[xq ~'MرcӧfͲ!E B8Ki4#L&ϣh.{ޚ0t:t6իgMbW]tckz+9l&((]󼽽vwwwҠA͛Gpp0fh˯./v|ذa/민,^ᄏ}B>B9?nv-۴i&Mhݺ5dffҸqc/xUiѢ6Z||<\y6l`->>M$UWTTmڴ!##'''3YYY߿)Sлwo7oNNNMgggK5nggg0Ll۶͛_!6m3<êU{?U/} !츺ұcGf͚ERR;SLfOMMeҤI,L6 yX._^zѣGsF#ݺu#00kkZ/^O?Mtt4͚5޳N{yWڵkg͚lfĈжm[V\Obȑ>}MVuFE! gĉuj;!<B!p0 !B8y,B`dP!H(B`$B!p0 !B8IB!$B!F@!B# B!P!8FBIENDB`cykhash-2.0.0/doc/imgs/map_vs_pyobjectmap_fixed_hash.png000066400000000000000000001310171414255425400234320ustar00rootroot00000000000000PNG  IHDR5sBIT|d pHYsaa?i9tEXtSoftwarematplotlib version 3.0.3, http://matplotlib.org/ IDATxwxwJ$H"(B*M:DH( "{iP/DɕT HR$`Bҳ7cIBy?ϳ̙3uߜ9GR!BèB!ϖ$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$B!%$BN.],wEt:V*8',,P/,, NW>x ܹs(x, ! s΅Rbb"ӧO_>~L>]@!&EfnnN&M:  =Bct:?NϞ=TRߟׯk FٲeILLRk>99???*W*Ub̘19l۶:u`aaA*UXh.׏cnn'&NH*U077|tԉ?/tt:KɌmܹ̙3www,--iӦ Ѥ1e*VHRѣ׮]3cÆ o''',--dʔ)ܿߠ̙3mkkk;vlaX[[e]޽Piiiݻ6m`oo%y#--?GGGhѢ[r9]>|0]v <<<|N'M@ʕ$B!%(M&Mvڥ+emm^~eRɓ P۟9sFRJ)^:u Q_~V_rrTruuUAAA*88X; Ps]pAjʕ-U]Zf Q'NTFFF_+ PvR[lQǏW{U꧟~R6l:t:t믿~]Q]VUPAU^] 0@ :TUK.U666k׮u̘1C͟?_ܹSKʕ+W_}ՠܠAruuU} QDu%8s:oV׎BkNm߾]u֩۷o纏A)N&MBBBW_}*UԠArSڷo짟~RN:jժUj޽*((HG)TLL7n֭[t\cBI(2 &,_nڵke[V3(7j(eggݻRZ2_۰a˵ennnJө'Nm׮SWJez{{+gg, رcuRJ PӦM˱LN2c[Ж/X@[n}}}cUZZڿɓ'u RZp6}ԯkW͚53XxbSN)ڼyDFF9+P*)))}̝;W… MQ8/wy}^011a߾}ڲs 8@||<~- xpr ښ={,nݺG||<ǎ6dC="==]{uԉdKy "өS'Y\\~%mׯcjjJ̲֭GU~ Uv Җ\FK/P^=1bWd;Qn9wÆ "_B=Ix8::711ޞ7oj˺wvݪU>cƌܼyL:GGG->1555xu 7npuOlٲr] @BB-[̜909֭[HJJ2><,;`nnKyY9!C2޽3f<<<`…֝>GɼY'!#O RJtn޼imddĘ1c裏7o/m۶ԨQC+cooOzz:ׯ_7HRѨQ,.̺SL0`Aʕ+˗:"w^´^? f;/yW2eн{w֬Y̙3Yr%נ\˖-iٲ%=z___*T@>};s9}r9yϓ"+n:7n$==6m,̌wy(Ǝkm۶]`-[>ә3g8y[[ljW_ԩS fye&$;v$::Z4sss ko۳ bȴlٲy\}wYUv ?Cpp0k׮G.]:۲4nXsիAPP)))9+$JzxlݺڵkǙ3g:u*u֥W^J.Yd nnnt`}vfӼys~wM/̀ WXnݺk׮%449s`eec .ElْQFν{믿ח 6н{wL+BRRK.O۶m)[,ʕ4n.5kF2e9r$ӦMԔueI31o<hԨd̙tؑ-Z乿ѣ3 tRKΝquu%99 \,XSSS^uN>͗_~]qk׮4i҄ &ʥKصkT֮]xp )5j6BB<̧#""T׮]U}UW^v0ٳgg>))IMijժ,ˇ*ULMMj֬9sA۷o+WWWejjʗ/:wݻ/ͳ<FuӦMW\umUӦMrppP>>>رcY|4hVjӦTe˖UFR W)>#('RСCGM+{{{պukcǎL#+i MR\~˗/SZ5 B{Tz=...XYYx{^Ojj*㎴GvM*6qppŋI((W/.xHv"BQH(M6΂ 8 NcQ񕟶|^?B!&;rEL2E:m۶ou(?jh!(<spp(}gB4/5533"I@Q2(K_(UEgļy ?zWWWͩX".%%?ͩV+V ##aÆQre,--Q .4y7={6ԬYk׮ѵkW,--\2֭+1>| ŋt:nʫuСCZ]RL"88X[Y:uꄍ *T`ܸqC[ߦMƎOrh׮Nύ?cٲeQbEzŝ;w155%..`'ҪU+-[ww,޽{*V_mݻw1b˗Ύ^{'Olc6lʕgϞZL0N=pj*J.͏?H5⭷^wwwʔ)øq ~^v- 6GGGǵk״aaat:vIݺuqƜ:u*s x6ΜB^Xԡ$|RJJ*X^J9i$Ƕm !,,ln޼l2OoNڵd,ZH.] qvvfƍ={O?>7cϞ=DFFuVv==k2d"""ի}ԩS3uTVZesRN;&L 444ۘRtܙ8~mۖ[nsNzIΝ9~8{aÆlݺggg%66V;11E~z~'ٳ'߲|r6oެmʌ38y$۷o… <8Kܓ&M/ȑ#/_nݺ (<)3(-XxlwU{nuIIIٳ*))I[q:[3eܿcw233SׯזݼySYZZ+rssSWJ)5o|8۶mzN81[qK.aÆ888`ccC`` .]2(Svm"##111zj֬Iҥ3թSG֫{1sL7oδiߵ۷yܹsZ}8;;k6m^'** x;_@PPz؈yu6oޜ?2ßABBp N8A۶m |VVVxxxh+Tl*T0>~8ݻw [[[ڴi峕lP'M3c|󚚚jά[ヷ7;w$$$Yf1o<ƍ^k׮̙3'KdvdƚӵkWV\I*U&,,L+v=9^`?2>9ydƐݲsu}:t@Yv-\t oooRSSܟ ,DH}aHt Sgg\Wat;-^Qz:FVVt6UbjjJxx8ܾ}{,--֭ݺuc̘1ԬYSNQvmz=_ϲ/Bf=zxzzѣGyW(,...9#GG`` ƍ~lٲwwwLL 2555uˏK.?hO2:t###WO>8;;aWV-~W:RN٣݋(33EvOnܸٳqqqѣٖs1 ! OF}bFKʟaR<+0Pz%`aƆaÆ1i$ӧ|8&""a___vŅ 8v{xȭ[۷/ϟ'$$Cиg}vb`РA0(w ͦM?~|14mڔ7x]vqE<'|%_ӦM?ӦM#22SN_?̕+W .(ggg9<;v`ƌٖ 0+WX(DqONQ$:qҸ# bܹjՊnݺӢE 4hmҥKH͵~{{{,Y[oѣY&Ç9={һwo7n͛7 zsrJ\\\hݺ5={Ԇ),3OOO:t@5Xx1+VdddK/)UT#44^~|RjUzI.]x7bddd`ׯƍY~=/~)Y8q"̘1ymL:`ZjСC^:}ŋTPx0˦Mرc^3ŋx1˕+GPP6mVZ̞=/2۲gf4hЀXvQg8Qii\K#Y[7WZa:UЛ&>>RJq] %''s*WXz㱳{W}vN8g >WjC$>89bը3wn%( ݻ9ru?9pW^/x)N|p0gJnWxa[`(_{c{t֍w}v=/_N>}2TBE)ŵ_rg&02/yh"lHxt֍ƍgyL*T\n(__BGakӦM<B<7.VPN3С#*$/[[[lmm:V!!D[/\@)~"KB!(twnPnXTl !P !O(;h o/Qx$B!DIW|z'L)*\x6W _r-ejժ9jb۶mu:]sa!%BbDǍ4l;t) @DI7l؀/1Ǐe˖tؑK.e[СCݛpI @^ f5xxS9 IDATnJB!Hٳļ;uTbE$_}Æ OOO,X K,ɶ h׮~~~ԬY???ڶm˂ 2W_}*U<*6ڴi nЎϋĥؾ}{!GlL>zi, !T\>!ˆ p^L\) `jj*o`y9x`:t(Kyoo_z;w2lذ ȑ#1x"tر(Dt…Z*_e/^NĉO5!Dɕz %m,pY#KˢK'NƆcǒNjj*ԩSQF1j(fϞڵk~:e˖[n̙3x}glٲ7n쌯/ ##___~g]3Æ cȑZ,Gݻ4hЀ@LMM߹~:ƍc/_?^OrrL2]Ν;s%֭˚5kX|9TRW^yK.NZZL>]e?:u*ʊW_}?\Kt邧'fff_5ksggɒ%$%%o`ooOFFvܙm9u^gѢEY+WA4oޜ1wIMM%))^a -;&=qY 󸫤-˹>9}SJLL,\H˕+q޻k׮e QQQlذ!8x񸸸о}{ &''T*HJOuwSIJ@O]qlق#1'OAaddvvvl޼%KwE\\'OڣO>h"֭˅ qvvvQreƎKr8x #Gݝ^zn)[,[n ;;;˕+Wؽ{7fffr -|%vvv_PZ5>FAtt4&&&ٳaggGll,]vLJ Ĕ)S>|8wA~zFɯR {{{Yb:t87nٳiѢAAA,_*Uhۚbbb2e | ͣE`ggGxx8M4!$$///~#yO&''ciiIVrCyFhh(ڵ{B|VM =߿ϕIVSOGX$fff4hЀPz- {nӴiSBCC0a,$$f͚e)b 4h@ݺus<ˏY4t:02zpebZ"M7upXZ]HHH ((5k 5kpvv֎}ei߾=ӤIٴiTZUۗ9{ټy3}cmmM`` _O?b <== bK,еkWZoݛo}F>e˖Q~}f͚-[r%..._T^]&!ʖ-KŊE:t(#F@'믿lpn2޽{,Z_ 2jժgAg} 9/###t:]⠸]M =n{蓓:qҸJB^Lxo "22 &p%rʏ?̙9sؽ{wIٴi>>>xWΝ#55M?Q-[,5jȶoMRRUTal۶M%%m>Q*-+H7q ļZKCݻ77o$ X^z%qssܣ_͚5c|'L:6lؠeZ~=J)[`ib~.ȃKYOړai2o/ e݌=s~,xkƍL0yѴiSlmm;wX/=afݙ=J>>>x{{sNBBB5kcܸqzvD2x:BTFLBBX:ss\.KEȧbƒG~,z-z\1b3DR^'$+S~)+7UVԔp\]]}69YZZҭ[7uƘ1cY&Nvz߯]~/Bf =f9$==GjiDEEqΝ9|sqqaȑ9???7ng˖-cbR).Ixx8Ԗ=0?ZjXZZgϞl{3{V B(@|p0h!VuX%`l0l0&MĞ={8}41 ]j+VӜ?oKKKpwwgРA :۷sظq# |||| ח]vq;޽{`̘1ܺu}oqyBBB:th;{!..۷o "((hf͚ř3gr,oaaɓYf Ν#<<+Vñ䧟~իܽ{"(R\KlFFT6#O,ΝKV֭:-ZІ yTҥ yԩS={?hß,YzѣGSfM9r$={w4nܘ7ȯ,ZnMϞ=1b6_a`̘1xzzҡCjԨŋX" ##ooo^z%ƏOR콝7o/G޽OT%n.[ƭA8L9T_N/TRܽ{7a`.\@ʕk^O||6JI'푕IV&O.*iiiөS'yM =n}}@)<D]RȯB!tgv-+7fLMκu dyU+3gBg->$؏?적;#OX=,D~t-p?=9QӬ7BQ~=^O=)?ySK<{SB(dk)σcǸ{nْJs@gl\aD@!BH9K>'$`ٰ΋{hJNQI(BMڕ+\:[U %K0i%_8 !{P0e7{E!!Bq. Fߗ0uv5h&euXH(6t„ :fժU.]!ēҥϨQ'& TzI(9www,XP[ !W޽~jIB)x>%JWr4ƥJ3K2,2e%r4>sY[M ժuX@^gΜ9TZsss\]]?:u^{ KKK1b ڶ7/ {{{ƌnѦMo&LNFy&}+++j׮СCՕ˗kSSS;v,NNNXXXάY:%KбcG,--\26mֿk;`7obnn޽{}6L2XYYѱcG,ھ};իW‚vˮ~AXYYQ^=HOO߹s#FPB,,,x饗 cȐ!ܽ{Wk{-gΜ͍ׯӽ{wlll]6G58޼Y6m;v,cǎt'yB?SH ۏĄ_eExF$|%|N1gNٳgPtЁ2ep6mݻ$Bܹs۷իWj*VZ֭[qvv& XbccHNNA㏜>}#F0`>kͣaÆ?~ѣG3j(-ZĎ;ظq#QQQ]ww|ԩSy79y$o߾DFFw}GJJV~ݺuTXW_}x =z;vp!Rt`D>3V^́O>9ƴk.{ӧ?>Wt^Oǎ9x k׮ٳ̞=ccc5kƂ >?>͛7tܙ0p@ϱcǨZ*ԒիWcbbÇYhoyB<}J)fs';?uXYRݽ{WݻY%%%gϪ_4gJI1+ssseU2eTB׷sNeddRJ 4HtozwssS3N:'*P͛7WA=zU|ydRƍS=| FiqjԨQJ)UٲeՆ SJ)8qㆲT7nTJ)rJpLddÇ2JַlR}Zܾ}[^Z999)ڵk222RQQQףez-cccN-;tTlll9SJ֭[+OOOs0ydcO"M222z~T}vZԡ<7Jzzuu\uFMucGk~K "##IIIm۶ٮ[.ڲ͛뉊ҖyyyatANNN\v-fddgQN!$$K.]:ut:} <'NPF {=BBBr?G4m4@sssOPP'Nɓ <xV&&&4nXޞ5jhuа]fMJ.mPa`ccμƒȉ'pvvz:V0l {C2eo~Y&M &oڴ)'Qn.[oV08"Q!g >'_Ez=agkѓNmj=p2!MMMw޼y̟? Pvm%555rWpٽ{7z_g͹֙LJzqeh۶-nnn9]fצ9^g^OBB666aaaD<ܖnYf>9B[kq0<2omp[(9Y:/SC2jժaiiɞ={U'Nڲ`ddT'33,? ݻwԭ[*UdDAѻwoٰa[l֭[6<<<5kjk׮MÆ cСںZjnp?͛7S[n`ETTw1ׯOTTUVjժTREu9]?ڱZjBwgvΜ @ѣ2hEJ@a‚ɓ'fΝ;Gxx8+Vw‚AqiǸq0`vy0?矹r 7njժrA"##yw{c?>ׯ? ::M6q6mDPPL6~-/>>>̞= z-Vݻwgڃ$*U{Z9SSSƍÇ9vC I&+駟f9s QQQlذO>֭[ӪU+|MBCCП~ x ٳ7nX6}X~YLL >QQQ?믿f_!DŇ8r汅xI(:u*'NO?ӓ޽{s5صknݢQF[mۖ_? /၃M6mptt7x㰱aΜ94lؐFqE}I}_:uzj֭[GZ 뇅+WҠAtBӦMQJlpYʊɓ'ӯ_?6m%ׯ1&ooo~GBCCiܸ1ڵcڥg-[ШQ#KZ^f͚1rHz_|E";=g$))W^y1c0n8FBLD)ճ'L6QrTN7+߻e8#3K9w-Zf͚qINZ&y/,z9sPjUquu>ԩSkXZZbooψ#HHHж/zMz>|8[nnݺ1Çk$''ӠA&O;wdTR%i͛nj3裏ؼy3FUVԬYEc6n܈+111yz={R\9Ér+Whժmڴa޽q$>>+W<7ꌙ= C8"Q= `jj*L2`y9x`:t(,XxsN>C9~8+WϏ7x#XRRRHIIpeƔ IDATJKKC)^GK=IivXT߻w h" @ʕi֬$%%j*U-{̚5 *L2,ZcccWNNؽ{7Æ tcccCɉ_c̘1eƍ4j`ر##G`ҤI̟?{RzuoUFftd>;!!!DFFry9s&;wgf׿ERViaaArrv#/m9&ziii?պ STIV$a>RȑO-O$kNƍdddP+T@\\\Zڵk$$$0{lfΜɜ9s駟ٳ'u;k,OeyHHVVVLLLptt$!!TR݋GY~"""HIIqZˋ m]ڵ;v͛Fչ=gϞն$''ԟٶmATzWn˗/[oE=Qm۶ۛ^{-68qiu{yy{U53 rPZFXih_3f0u+ֲ7Mjw,Aqf20 t08yO<|>`0m#F``pd2l6;3;Ow110 سgfwoc ߓ[Z,6 'ҀO?u~edeeaZ~@rr2R弫z|~-88?0~a&ǎ2C3FBReLr5  pjZRaȑ'==ݡ}iiBߎ'Nص9y$X?{6mӧW_{ eeeصkz)^3سg***h/R~;v 3f̸%_-[8y${=DFF"88;{bg4iԩSؼy3ֿ|w8q4 ?!9 u4Xsm\X~=JJJp1̛7jvɓm3~`Μ9(--qq,_;wb֭Xn~\׿,Z< ^|E$''#''No3f V\٥,Ys!!!}nøqp= 22 9˗cq9رg IR|hiiw܁iӦٖ0|hhhwߍaÆaݺueN>>|8c#tyytP$#v͛^u9 o! Bۄ/w- -"l߾K5 : pQ`0t6E,ux88rXF}vh4]JѓtC8hbV}p~{`kkoQldggwԩS1uTgGDDtC,uuPM!F\I1f([\&r~i[;YQ 8Ypm(<2R첨s3D\"FD҂Ya8| C @2 LW{`"KDDD"V\xaVϐ!bEń<ÖəA@Ւ%_b^+߈;.<K mui>Dbjl$D/+vIaxd2ۥgZVF4771_xcUcbZq%a%ϣYu#藿"DIB>>>N;x88rHRqyV}YL"T .$H}vٯ& {ѣ4 88r( iRG'g"," 0vLd2f3pLqL߹ _<(<?g\q~T+,M y+EBL&df"K SHDDǏ|LFB gSHDDd-gB7 V>݆~ RB첈ltS`jLNF웫!,"; DDDNbh2J( @u]@"""'A7 <:qJ&vYDb$""AF?1-'N@'qJ 4(X[ZP>{6 CPNj]Q`24 R__ĭ[ bE`‚h!Q(oj|eu  Q ?xy!"C첈:.+jH$~U{%u  Qh֮v:@/E:I;bﳿGHD+"> DDDPG3'rED׏g߸P Gg+"1 DDDhk6 ?@"NrqϘ ~F!/K첈*-gB7 V>ފ~ RB첈'L.@=5dĮyR__"r*@"" S\Y ŀ[@"r:@""":B\I1.%&Z  7 {v~۶mHIIRDJJ >qH$?wy+z(kc#gDˉ#("r[nܹs`:twuƏZn ''8|0rss1qD|v~_gǎq8DDԃX[Zp`[HW\ EbERnWXO [<3$v ]g1\aሏ'|7YXXzD 0LPTȀ\.wjcccd2gA^,xg '0<<2l_uuY6]jQQQǩSFT{vP.ƕ}####5%۵K“>#r@  pjZRaȑ'==ݡ}ii5VEyy98퟈תy]h/6o.B&;z Ç#==k׮Z̙3'OFLL -[3gF˗#++ ~!v܉}xbETTΝ;^x0ahIDDSGZ{1pT+"[hZ,YHKKÎ;PՐJ9r$lق bѢEHHH֭[1bL&M6QQQ{uVrDD:_| /[n$"QE|ݻegg#;;>>>ϝYPbފ~+_T,zӅ qTX4(MH]&"wHDDYzj*qA'5 DD+Xꠞ6s % ,ܞ3fq_R9DtM DD֬F#? ~ i` Cѿeh DDy@닸k(vYD= %jEWD@73te@""r; ,E݇2b ;,"HDDnRky@"A".ȭ0[Ѯ_5ȗ^DЯ%rEDǩE R{Do,z-[P y7W쒈ܚSjRzgzƯByłp$e5N!"p0gA0?v .D_]D7eE7n'|bCpp0FU%"^  ~#b$^Ns#,+8p+WGc޼y-sΛk]|E+!U(.p?1p@ۑ'xF=3UVǩSahLJB5]Q3jR; 3kPO J(G\z.q L6 z+N<_#G-)^0;(ĕ+,L첈z%|7K.a۶mM|A<î-!kSgD˱c!h"\v088+Wt/jDD䆬F#~ C D\I1]Q3}]>rf3K ""7"͸3hܿ__ĭ]D"oVďHOOZvf DD& ^oСbEz X,Zo4{""r WPᇀL/=]첈R9cڽgܸqŔ)ST'8?)40q0@첈.?}tL>V} Ç#==k׮Z̙3'OFLL -[3gF˗#++ ~!v܉}ZE CXX>r9"##xuylƅgA"v͛NJ,"ݲ Sxx V%KiiiرcCZ O9r$lق bѢEHHH֭[1bĈjEEWD.G?"y\Z-^|Eڵ հZvt.v_۽{öldggw#""G +P}; !| |. >(N>}+"j*,X_~%Z-z""r/uŋaӧ#DDeQWWn{8,vMDDNVk.̟>~MDc,wB;I DDnQ1g.`6#׿FEN\p!.LD |~>c H]vu}>ꞈ\IkS|D̊@g .;SOaΜ9xg1d/|ͮ5 ?Bk]|n+WBT]9`NN`ԩm@z8SUSrIeb"b׮O첈ȉ\Ϟ=몮E:S`px,(H첈\]5Ez4ϜWT6+<\첈?#?r}Qmࣃz kSg> E\I1bED. }Ń>xv80| +^bED.hZo""=!A5NN,"r1i&8l7شivKDD$X\*$r9bX n,". SLA]]zL2U%p IDAT"N_Y 1]#.`zW;<4R+rEDԝ ̭ DD1c., Ξ=_-ud4V"^\,+"6oŸqo{MPoQ'}C#\^999v.:} ^Z > 3 "˞ch[]],L\\vMDDWiܳrЧ@쒈HD. NԩS~mC4Q9}U7f3+Dbs,>(!"A#歍Fwe @pYoqA$%%jDDԁ'qa擐qb\첈p?SRRh\=uVC^Cl,7HJ"epx{nhZz?DD*LAP1u bED=.1csu:TQy|׮A#vYDø,ڵU]Q;,PO3B|I .vYD,}ݮꚈb5P>I=Yh(⊋!d4"\Gvծ}pĉzU0`x{{cذaػwomۆ(J>{}HJJBBB0vX|UQwҼ5732D܉w}gO[nr[nܹs`:twuƏZn ''8|0rss1qD7x`\=ۇ#33.]&"r5톷Y h!|P䊈ݸСC!H ;%%%]oŊôiEEEϱzj,[̡}QQ222PXX(,,ė_~"Gya&" j{˗̝N䊈,={kT>}ۻ}F< ~fgכzqLyxxұ^`{tR_: H{~-4 ֭[gOo߾-[ /RA*%+#6&>g RCq[O?RmLcbϓƣIDX[[YfrٳxbFjjj L&s8{W]]pMddda8p N 4Ŷ{z=bcc.1LPTȀ\.wjc7IsY*^^l=w#a HplqLnĞ'G 0vXc}m۲n{oooNY:S`<>q! ,"eA?nW3gt _DD,(6ӧx#vYD 9=>cv_?Qc5P>I4= Yh(JJ zDD]agwIDԫ F#?=! @\z(o vYDԋI `6иw/$>>]&.z9@""V+*_| \~n," DD"_}uH˟F]y@""h^_MQK"03S䊈ȓ0u3톷Y p!'<(rEDiQ?}A裿""D DDD駨\" 4o*f""T DDݠ/Qs 8'}{H$""HDb?=0_!E D$*@""2|_OChi= z+dbEDEZNBi66w\첈\X^#G E\I1b.] DD7H0q90 [ʛn,"kb$"łG޽ v͛NI,"1]'jE/^&vYDD?:Q}@*E̟ 7J첈::hVMK8.S䊈:oo",@EkD}\L8_5 | -Zo>N<x;Dbmnnf> !&AP]&K%+V //ӦM?իl2EEE@aa!_~%"((*=:jŹY\t 3 }D"FlFvv6|ǰX,3Px/ "wa5Pe+Űh4 ~D=EW)CECC;D!5,ii>V!^Ypȑزe .\E!!![nň#ϟG}:tݾvڅ{{-_RoL4ߙQ ^EQnj#N֜D=;`uho ziiH MU{ v_۽{öldgg۾Q765]9w9cbL?TwX,V ԝ2MN֜8˶o_T"H$BY$"emjBͻ[-)C3\ځȅAq5e^=`68 R!-5赅>}Dz4[+.Ecc>s-]jzW:Ij[jx 94ii>߯eH| D#w-.*Df#r"Հ*WTKĐDI7Թ1Qrnlqq$2Mj`}; $HN18d02ά G66^ ~55y\\??bp$ӵa,mcb'v^JX ~"TM݁Duvϋ?:*XqN6IL[0Zm}mA/)8 ~H JD66B;Еl`#"APXi7I(L mW.^}I& ;݇@"OX"VFԼt%%Զ(_~b#jYg{dqD{fC;o7Blk ؀XH%vz%OƟD- ~x?_cj='Bv^/ d^Bp^O 5o0if='\9p|RUuӐo/oހ?y%f#㘭f=m[)éS0 fQ~Qvk @ B['095 [1z-qvj^jX*|D< S\3I{]lhg-b;$|4v DtCZߡ ~jk\ƽ%%v II{??gRHDЀ͛}k7`xx?LM8=jw)ooP e\Xz&@"K}}nj ~Gnd1dIۙ2MԝU: -2$&G u͛{k#z=?r_Vӵq,'jNd59׶^jx*RR j"a$Zgz>APPzVR|2w8>#7Hdj"b$vY۴ ȭh zm4j[jx 94ٶJZX\ DdǢCi3t_Y7zQ##v4.6]th%BbH"ӐQ&r2|y&@"q#)&O> fcӟsh'7d[~eH  L0LqbdR~s1y8^Mg~fj ~RYFd5ᇚlALSjE8]M OEJX ~"TM>]Ռ3uglq|S`FEق^ZxRRj" Q/aZ4GX?AP^_n11!bH[ j"*@"7gEo;ٳ0f uk;=QO%X9#M1)Km->^^ LJj=Gk{DsՆjv IIA9#a$r3kjg,wLM8;.~U o~&7wh'H`A E  0@-^>=`.LN֞]-ӖtiXOgJRh|NDa$55Еl\MNF̙chB$;;жo_B77#H$BD0Pd= !rJ BIGGdnrh[~%-< }}d2aǎ3N弬KD&Z ӟPYYTᮻfm۶aѢE8}4tRL0ᅬ5kj8tB!N݆ нN.{/A0IơCmA/-, ADp֭;w.VZQFa͚5?~<=8@NN?`„ 0qD۷#F466bԨQx衇0}>$"?Ϟ {/^Xo{F[jrh%>vv裂 Dz +V@^^M(**իWcٲe틊B@aa!KwΝ;=At fyj67-ipNΡt+P_4j=>F DDvVAArss1|pcڵP՘9; IDATs&`Ɉ3gF˗#++ ~!v܉}tPոpĉZFFFvRob<_ݦ{W7K#pTwǴlOcд۾`Z{a4j""sV%Kiiiرcjҟ< kȑزe .\E!!![n}LbzҤI^z /^wn؀Kk3X#7o=w9kwaeDnBrh2RRĐD+E"@~~>}m۲}q^>"TND$ D=u۷CF_"tNLl5lYcc8;n|˫( +aoP .LD Z-jy5KMDi@B& G!dd1=1ԝD١n!} r:Yz u۷C0>6A-d~?K NNڅS5} II]o`X-Vر'e:Ht  oa.v!C6u 22 h25dIM8]{6Pz646A#6 ukwODۋ* C-4nH}8| Ď.78iٖ_9[wޡa//MDd DYQPi3Lׇ( B{ Dmm]n+GGW۶O_[k+҈<  y8ԛO|ZnMh[Tn8ے+ɡ/;&"0GAamvN{D8S lo5J z)a)H MB"@ʉmТ @s~W=`ȵ@ׇ M"4W\ hJ%^n*+S^OQ^cACT`>cW%9&^ AN&n iUvލ#Vs*?oU@n6#r(DU6\æ8-!Gi]u6wFR1[lhf/mΖsaux`uἪ:_?62A\ Z:us*9Ǯ'P_,GyVlqS%&"jD<C3U8*$z/ 3ĉ+qImn 7vk!ܵYď5Xa3]:=:V7`sL5XP痥-E@/Y3$m9pMR[8d-{.}w@p:$>eG#WơˇĤDq^nHNCc7 WjZc:"X2X}z=p!X8' R̅ @ 6Ԝ֏@" MX "5 G̳ѣ|8c=#GpVъRV$q^HJY\pUyU٬^8\8$\`\-oHLJN̅3uRu*hd78d-U;<V c`:~|~îqxqWOW )+ɑjN,HJA-pڠs] O55UbZSk#/۟9 QJ:C?k fTxaV`R V_ȠA0:[8F vv]Z!> jhDѣAF#6ܴ./{*Y:t.Q].]1#tJM3E;rW[5 W\8KΣP+ A * / Wg_8AX/5J(c͉@֬EEj5_ x<uyr/FG+^Y`X},DuAHDS"2Ʈ@Wk6 \ZX ̅(?AMtr h"U g4@0! ✸BV 'pjW[]PGx]߻7.6-WD } B@jDHV(p+`Uj셫jw n+"-իū4ؠlx5V恠@Pՙ QBЫ zF#=:EIǧ10pnӨ-w}q(zQLo.\#8VqL+.iQe vQhnXX3#<*XUuQk` n g ӈ g0BL3FUOc`@vøΟGuNjv|ݻAǕ*0㠁PYSNIʣ8Uu UʕQ293oҜ[xj*a $pV jXjj ,uQ^5,>Á=*@0B;!(NY-Vjۊ™Oc`rrv>T@u2.`4{@al'>8W8h9뺆5fEsTgq7՜ n8*T[*`X`]F j ΃Õ-EaT=Pc4&AIMEHFz5o k&q_ ;~B}Ϟ0 AtԨ'Nµ{ ogj'X},0o,䰠VKU5jdK pyT46@@x5^YtfqK@/"9Q^pWc8dD.Qbúзpgy|))8ZqbR^РcDGE#*T`CUc?g-ũ] F;p ̃(=p0*l.f0@yp18 CD&7{;O&0HI^rUOHM$R)HHE99qqbw%5-op"Ϋt :D(s,w¤t9AF*F ^G7[yc&0LP<{/:UDyW)-qpw\@1)1 &{iDpX`TfWKl.Nx5A w¤pKQM9yp+'3&HBz>vCΝw1~NoQ^ ݻŤU\ U%@IO5.vCitvw^vջg*$ I!I^9:e7ij́[B: ,.*[{>O :m<8c ΃7s}03H ^ciU +?۱|r 0׭[_~'ODO q] 2\d0`.P?'YvM~234>=D˗cx'k֬All,>c<7qW.-1cQq8.Do…2GQ%hQmPCZ0BM̫ڧEZ|Z!=COjդ)hqڴoz翹`Rx`R`RL4Jt+QM&&y# c*@˅|̝;Wr}ذamk0sLɵL,_P\\2 6Lh0h 6:N8Ns,ykwbE9j}jtw G-PKʜ={< bbb0m4\.Iddd@"55NH}ZbL,YL3f1&%%%hzu]NСCgTVVbܸq007n$e 1h t:$$$`ѢE!瀮[ݺuFAn_r ǃ  %%:Xh|>XVɎ;# >>2 ?%[RSZ1qݘ3g cpZLu"֢]T*{TTTDӧO'@gΜi5Iff&Z~:p1ڷoObɓ'SBBeggSAAw}tw!""Ct}QAAeggS||[_|!ߒߘ1x>S:rQ޽)##C[-&ps=4ydɵ4;wn3(//'CDoX*֮]+)))!\N7n$"7<\N%%%bO>4 Y,""={6I矧{WfQN(;; $&9sPzQ\\-]Tp8l6;CDDEEE@(//Б#GhŊd6pe,YBD0IDAThȑJ?33Fu m#FФI$מ|IMDNKjcKh()oϞ=@V k:nA\.1l0aÆ!77jucX,@TT ??n[xmCzz:2p:a^^^H233o>ƈ#HcLW^կ~6mڠGx(++U`РAf[,sl6K 4F, .bvcҿlݺǎdω(Zk2e|عsOֆSe(0aY&I>3nsaؼy3ZmVC^xb@=p!\ǏT]7&}O?Ň~?~;83f >>&Lht]o4%%vcԨQ|Xb^Ƅ5{[(ߒC~j-Nضmqqqp\ָXTVVv_Lyy9J%[TLQ^^ (J(J7߄RDlllŤm۶֭Z׮]qYf]pŐg??Yf䥗^ܹs1j(q7nfΜ)cLjIoL]n&ۍ#Gb1aW ` Vll۷jLoAJJ~FFT*Ķ?JKK27oFAFFX~6oތ^zAR28pիƎ+~n1ׯ_@ǎCRR %%qqq\.HbbXgݻaX$ev!.g͈GrrXvo B&cRWKjcrǏc˖-ǘp3V nSQQ͘1 >}$ol6۩T|vɓ)11lBt7ɐ!ClB ny2sL***[Ę]L~1ٳg)JOǏ>z=}bKlSaa!=-?wNyyyGwqd{*ѣGSaa!_Alok.R(tR:|0-]Y0a%$$_bbbhb[=&6O'o-/M>(%&&ҁ$sN-v}8l~mJJJ"ZM={NiM4ZjXLBQQQgJs1bt:)SH( "ھ};уj5%''ʕ+CRcZ? ǘ?th4FW^yHPR4vX2Ld2hرTYY))ӀHP\\eee[[}ԥKRTF֭e} VON۷'VK4|[=&۶mkDŽ e1ucR\\|۶mec®ߌ1csc1 '1ca@c0 c1X1c,pc1f8d1 32ncƌ] CTTd28Ua&Nk]kN7nի}v"&&dOFJJ ߏKc 1y^d2M8y$ڶm"wPT?f cVCcڴi={6,ӧCC ɰ}v!ɰi&:?_k׮Gn)Sh,X@Sr0{l$$$`0wի/ݺuF3glkNNh4ms΅":u*Ξ= Llݺuۡh_]r?99*ƌшx[2=ڴiAp,u] F"ƍѿ1^?0N<)~]JJ Gdz!CB|Ɖ'gaݺub^SSYfa޽غu+r9x |>={[lAii)֯_`;fϞua͚5(((@ǎ)y?>^u۷J&M;ػw/1wd5bAQ%n3gڿxжmۈh۶ml"Yd 'Oמy̔w׮]̙C]v%"'NL&I B#"UV:p5ԥK{d4џgJJJsƌCC\{饗[nIIIJd,Lu]y۶mQ^^~]ω^GjjZ{pd}zQPP"BΝa4WNNdSVÇO>ׯqFׯZ~:mG]}Ç{-iWqq]III$9y$ƌT ={m8y$n* sXǠqm۶-fo~<Xtփ0d2qH1s?Lv6B@~~> h?tĮ!DR&ئ>m۶A!#rԆ{!1c૯_W^yk׮O<0ƚ2B{JKKk7r.N:APGz(//Gǎ%&Onݐ+Irssa2ФܹSr-77;w$ +-- гgOATZ[\|Ƃ 0dt2j$ujZۍ}k׮?Ν;c̙ؼy3|IZI_k~2Bt:{Xt)c,X=ܹs5k=O>oO\;ǏQ\\{6lФyp9L:G/+5kVyuV8v֬Y/I]vaٲe8v~m|bx?86mڄӧO#77 ,*HDGGwʼn'7`֬Y2mڴNƍqEX, K/7_ݎg}QqŔ)S}v9sv޽{@2ƚ3`ҤIիte˖aذa7ǏGmm-( L:=xժUx/ӧޤIHH K/;DTT}&'={g}wxWѶm[,Z'N{ d2_Gff&P 0|L4 ?#0p@^r9֮]iӦ!==]to)nJo&-Zw0`@CK.øq`ЫW/lڴ B˗1~x\x111x'pF}=cQc'0d̘1EscWC1ca@c0C1ca{c1 '1ca@c0 c1X1c,pc1f8d1 32cNc15SwIENDB`cykhash-2.0.0/doc/imgs/map_vs_pyobjectmap_only_insert_fixed_hash.png000066400000000000000000001406471414255425400260700ustar00rootroot00000000000000PNG  IHDR5sBIT|d pHYsaa?i9tEXtSoftwarematplotlib version 3.0.3, http://matplotlib.org/ IDATxwxUwz% K A H)"=&t)RDHJQ) JhQ::AbL@ "^!R%G̏e(!3sgRJ!B!LYQ B!,)B!LB!&F @!B#B!P!H(BabB!01R !B)B!LB!&F @!B#B!P!H(BabB!01R !B)B!LB!&F @!B#B!P!H(BabB!01R !B)B!LB!&F @!B#B!P!H(BabB!01R !B)B!LB!&F @!B#xxxЩS|šXlYđLPPᅲ}SA@@6N{|:t H((Tʕ#"";  """//ZСCK(S¢B<߬yW: 8999 =B<ÂtDEEѭ[7(VڵkZRdIѲeKjժM2qD<==B 1"מM6QNlllT ,0X)gҧOF,\h/ƍRJX[[쌯/O&..2eNCKvl}f[[[7oΙ3gHOOg„ /_bŊsUmYmR\9lmmQ&L ))ɠ]@@ǏS;Nk+DRBgV``?~ڱc3gWWiiiJ);7I *]vBMsN,,,TN3w-emm}]ب6mڨ͛7pԭ[܇tjjΝjΜ9B Ikۧo>mەSZlڻwZtիRJK.wyGjƍq}v1 ! B<ò c UZj6Yf^z ԝ;wRJ+>Svk֬Q믵yJөhmڴQNNN*))I)sخ];UbE`ȑFݼyS)ԩSvڕk]kdVn]͟7oT.] ڏ3F.z^+@;vL[5|u>jԨE)@J)֯_cSp&(r X@߾} {聅=h~Xr%888YSo&ٳ`~Z[>}cٳ_;;;222/>|m۶QjUZny0a5dϿx6?O>`nn%͚5 &&h_>}ߟC aÆԫW+++ ?~>Gy9s ϟghB'i J*ō7y]vCnٲe$%%1b͍7Ю˦pqq1^NwmGFF_|/____kרXbr(J,i0meeTiҤ G?&<./_틵v-S8z(T\ݻẅ#\2+Wfyn;{߹}}8NBGF B tFF7n0mffƈ#YhZZjZRJk @4lh9Œ(Qsss {yzzPL޽{|2Z뀙K~VD vʊ+ Ɔ޽{kҤ M4!33~/1cPlYz㶳(/ٟ8 !IρPkגA 4+++Kll,#G4XުU+VZe0Æ $%%i˳qvvvhт(ԩ+ СgΜNM0m{t:A ٖ,Y:X\|0VZ믿Nslknn/vJ}9KժU\2K.ݻ+$0&=B<6n܈mڴɓLsiذ!?Cm۶TXÇop૯b޽t777RSSYt)@Q֨Q~1o<,--iݺ5'N`899… ܹ3 cǎ͍/c]6uZj8::}!D!(Q(B= 822RuY988(GGGջwo?Nxx̙3s\>,--Ur԰aÌn#:v֯_jժ3gA쑶˖-3?`UBeiiʔ)5j>cvnRGVnnnR9;;;ӧOkmvޭׯF%;{o[t]nGj:|||*S4hߍF>+{{{uqռysekkJ,  RJ} P#R*""B]Y[[RJf͚-[ݻwq)gggecc^y:bŊ)kkkUreQ'NT˗Wfff9nCRjO!# "88k׮Qt3n8/^̥Kq8v,гW_ĢE!Ü9sE1t'R۷?XYY=cB)0>>>ѩS'>'6mIHH˗"̖333{ !)SBN\\v+dB !;˗ѣBS%=B!&F.B!01R !Bz._H(!B<ݔRܹs˛|/_յB!Ct+V,0 .]*2Dzz:;wm۶XZZ>m?$$'$'$'$'L1 |٧} d~ "0&91&91&91&91d0˷LķB! P!H(BabB"##Z/== RSSxØĘ) I,Diii\r^W) .]/x$93Q\9:!3F Bp/_++ILLdoRy/ɇ1ɉ1SɉR4]ƅ Rs~)-Zg}ƕ+WUͣI&߰a'OT\O>_][qF,YBdd$7n **ziqk׮7rxb~}4z==z4lllGN$'L)'XZZ_iY! п!׬YØ1c裏I&tЁ/>""={DZcG9rDkDƍ9sfpuuʕ+`СAېvsKHQtE pΜ9 8A0oϛ'2Jhw1ydezكHNk8qD}]m:am۶5zpjj*.].VJq,sDzz:XXX`eeRWvlyq/}>-9yZNRSSiӦ~Ϥk.ڴicry͍Sֵ(;8ݽ{W75jjڴi븺9s̛3grss3j{\cXbTW^7ރ*@V)n߾ume)))ԩS*%%@ۺ_ffu|EfѣRJO>D_988(WWWdݻwՈ#VjW zX9DEE1|p ӧX`[laڵƲj*<<>0"##UVܼySsXv-6l :: ٮu̞=GL.]HOOޞ^zb>$$ݻHrr2۷D=zuֱ{nFiΞ={a߾}|lڴ)KM4/^ɓ';v,cZ޽{dddd{ѣGڷoޟ3gcaaA޽y?>L2Ek9pJ*2>>ڲo>># u$#=/PS3ౕ^+^חÇ0w\é^:/^J*t:ݵիWΞ=͛7~z d]ǴrJ" ;;;x@ڴi˩X"6mG 4Fqeʗ/駟?8BCCIIIaŊg/s̚5K6ʊKbggGZ:u*ǏgڴiF3g{J*qA,YBfXp!Ŋc闪Uj۰ݻ9V9r$ڵ̌ѣGӻwoCƍ8p ˖-ڷl`%KPD ONo6rڴiڵ/E8!CQ / ^h ]pGW`Ϟ=qSNʕ+⋄iŋ ~i6jԈիW3i$&OLʕYf /f˖-_ի?((HtR*T@۶mⲴdѢEz*UԩS1bN3N:u:...\zڴiCjh߾=:uIbb"J2^JJ ϟצ C[E@ɒ%VsK/QV-VX Xr%nnn4m֭7Fu5Cbb".]2(N:EjjVfKKK~DGGӤIVZ]cpUL޽{$99辜1{:::B̀ׯ`.-+ O$Çzn4{t=~Os\־}{ڷo6+K޸$ܹDb}Nz似p۶mcу֭[~zz=ʕ{ނ0;`РA|L0-Wy 6*Ȁdi֭TP`YvmH=6޽\vy5>>>n *En"gwdM)4RV?I:]O`)ۿ={gϞtޝsMB.|X>n̙3T^][ޯ_?},Xɓ' RfM/_NRRV/=v)))ZvaXQ<5kښ/ҬYcS˗/F@?\,Z___.]׍>|z`:R!+]?6kٵz=ݕx͝;իWsiΜ9úupqqxn^{5vA\\bҤIoynÃ#G | ԩSٳg'N ҥKkiK(An?~O87j*pLBa2S`?"kG"xgbDdx&3tPN999L~W3yޣWsq*D%^{߿U͝(L쌍\%ύkug(x4.LB!(~5 6 E@ @!B`;Ww`_*!B!`tӬ߀o& B!x<2#uYMAIO#MMB!xt7aufixuT"R #͛7g̘1ES) 'd(˖-] ^zEѣ`޼yEq:v!SMz7㑚7oNz_~ e͏mxxx0f#J"zRnB17kuT"R #%K,}y+V!:NG\\\1غu+uƆ_~?$X~z?#ܹs?-[bkkKR2dF СCIKKӖ X)ŧ~JJnݺFqL2Ek9pJ*jpɓ'op1G޽)1>ƍ#**FѥKnܸagƌPNvA~5jNbɒ%,[O>D[̌ p /_޽{y ϙ={6Ǐ]vt҅gHf͸|2[lرczm/w&**vѹsg.^d]>QbEN/â]vt֍ǏftTSNJ6m 槥Q~}iҤC]XV-ծ]`޽HW^eʔ)ݻL{-,,6A6ѣ=~c:u222HMM%))$!!˗/Ӹ3H7n]Ms6))`~'._LFF)))F_dd$Ν38BsΜ9)[)^x≺|K{x3+C H#js= 4m-li N… l۶ݻwӣGZn+Wlb:A_2aBBB߿\)e6md?ٲuV*T`[ۇ?>?/;*د]ƼypwwksS<w^Opp0ݺu3ZƆ ___~mMFɒ%9x $==w,Əώ;={6/t=zʨQclB<5zAUp,}B:'ZR>A:;˂]#ɰ DϞ=ٳ'ݻw}ܼy///㱰Ю ,(+++2338Ç[8s իWז׏ pIe5kdI fffTZUkw1RRRb888PbExj֬5/^Yf9\N/_EN8EҥK\~ݨÇy뭷 {, ZjFFFRˋX^x!odddk?k׮-pLNNN/_jA^z :yf _Ⱥ&J9////N<{QoZ,~Ba`H(V!S,Soܹ^ӧOs֭[ ŋukc8t&M~s9r8_nЋSgN8A@@K6[D ui۶Aַo_lllĉ۷wy???*d8p Nb۶m2r uGGG{=Ǝ9ށN@m?]oSC ?ɜ#hҤ :tŋ9gϞq1ѣG$%%ѸqcfΜ̕+Wג%K Ӈhoۉt.R$e@s{=꣹>sb 6ӧO`lڵkeժUxxxhcǎFdd$^^^jՊ7oj?wk׮eÆ DGG`||| k1^tnݺKtt4 b„ yuVuFǎbϞ=x{{YdTXSj1!3y5\: ŠF߯OB?zQG%BFdd$m۶5߶m[::F۵kkRtijժ{ǝ;w SX1xW(VCyVN:^ @@@TVQFsNmdd$*U uΟ?sww7(FLL Z<׉UV_!xj_u7ThH'rU=ׯ_'33e/[,9@sӷo_<==qqqĉL8cǎk.m?F9;;纯wr]m:!!~}QJ+ks'(~wqptxϬuu,,, tdffWϟg۶mٳ=zЪU+֭[Gff&ʕc޽F/^8z9wo ٧wmݻ,ir/'rOOO<6MG.'Javsκ,J_]]L扌QJY+++033cmmŅzaffFXX...憏o3f̠Zj\|m۶ѵkW(+W&::7o@%HJJ5Fb…3d"##Yz5899accNL6m^:={$##۷3~x<==_s֔.]RP32bkkKӦMsIOOg׮]iӦnynr/0y3Gn& yn?쟄B-hРv_ڵ]渎vbرڼ;wҨQGɓS\9m?o_套^ȑ#ܾ};}Y[[keiiiCN 32U6{S 8{,4lؐ0,,>Zaaa|G 4k׮BӦM)Wfff/?~xyIII,Y2|xxxaƎŋy饗dmٲ%֭cڴi̚5 '''6m-6mCJ*ܽ{GS?nEyZNr_Aژg:' M~н*ByL}IիW+KKKߪSN1c({{{ROM0Ak/(sss5sLfΜ,,,Ç67nPQQQj֭ PWVQQQʕ+J)Ν;ѣGՅ ֭[UUUFF۫:uꨈj׮:uTvm۷o-KIIQNR)))3TnRFaLrbrR4yf#{=9tTY tRjvu=xy6~ `Ϟ=qv?_|0v!/^4KQF^I&1yd*W̚5k Fnٲk7x $((+++ILLՕ;hpthh(FFw҅/P!Br 7׽{wwu+7%Kjժ|ۉ7l0BCCs\֯_?'BX<| ?J͡ )VQ@+RlmmMV|3/^\8}&eMNs\zwEt:ʕ+3nzz:?3M6;H>r"91fJ9EZl [[MWߕPI?9< BaLrbLr"9w{\skx}1FQG%LB!DQ|n_R{p{9x B!s{`]MR/@uPRQG%LB!ē~ *CU`W&F @!IaT887kNOXXm\$I(B< Nnʚn6OH(B}o`fWwQG%LB!Da~Bí'z &ER !" BqwT-ꨄB!ckO PB#B(gA]%`i[q q)B!# ~ǾϚn<ZY%waQǿ ,X4+v(KEM$ v1{11$%jFƖQ5b[$kl "EP;ϋ(Y4`)8<{fggν\ɍ4B!JK 4W5U H !#!,]% B񬢎3RnJ$ B,m-#@͟mUsW%P!xޙ]-cֲx !OJ_&É#g֘.!4B!ēHO!wP,h9U LB!˝(X t1wUB<3iBǹy[PnjM]E@!Q.C@ UNJ& Bi/C`mk !3at8ĸlnK%DP!/l v;|ǃJe޺c !ѰOF o.=]B@!9C);^0wUBiBlW&ȼ jA"_I(:h7E/(UU BQ(Կ̈́ʬe QPq ꊵ5nY۝jw-Rh?Mb^D̙Ð!C:t(,ZYflHǎ>}:ӧOg~z @DDDlР7o^vwwO?deeaac/_k^>z{{{bbbr}OLLSm$1bD1 666oL<8{\3k,>C{TR\~*S2&dLL1q?HoQ)zKhdZږ1MITs`v2 X{+E1Yq޽KΝ)S [n/v˖-INN֭[OբjM[ZZM~(0%cbJT >l2.8 3sč(IQR>,`+++<==MN+ѪU\m={$''フ۷o?sI)_SK!D!kz?ޘ>G-DI'MĀۛKȑ#8p 3ǏO۶m/֭۶mc޽:t({ DFFrM.^:88p]|||HMMeڵ$''g\2;v7666cṞBQć:_Z[] /*! |o磏>":: sN1UVlذ{3fƍiѢE6۷og˽{>`̙d֬Y3G=Ḹ`ii… 4i777>#ƌoc!"E } 59AM`a( dѣ=zt۷d]Ϟ=ٳ##_o߾=<Ν;ӹsn#8}Tm}7BYxQYB!.ECsාu^Ui%D!' BI'5喣Pk[E4B!$4:-*! iB-w"^pXB[! BF )P8٣ZsW%D# BaPJ*&(WU Q$I,BMQ o毘SQt.XP!DςS }6+lYw3psS,%`!S=m+{,TL䓘>5?_k' !(|os,ͥU|Kař|w;2tuʸp(-Ot !(\bp& }6@u/sW%ޠg땭,8x콘| +7suś4B! A?d:ƙ\]of\sg[g&yN%Pe|' B E.mo T0wU"]IB@H[+[F7om_,5fP!y 4ʼu<ЅlbBmA}h]^# BLރSdo1ڿײrRt)tсa[Օ\ !0{n+2wU"(®]$:%3T<=\P!DzH}~ׁs+sW%ؓ>63qgp(fyU*yEa BuulIP v5]QQ=1kA(ІC1k k3W'& Bsr-,pjiZeD+yyJ@!'l@WgCJ<C W6WX,#`]| Bgx q \ }]xBLɋIONS{&yNµbŊO&Jʼn'pvvΗBC`܆Ռ=62wU (žk{2nPBm./T}Չ`p)WAQF^BR + rJ<ӷO3lBoP٦2mhNӱG)q,oxP%;ٗ{M*O6MܸqVBBLQ x(Pz~ ڲLnu _~4`Nγ1,d&f0 OwK1B! 1}z0.7 N^ͼ3Y{~-LTV㚎J*t:sHN!Yv* лy { !owp%P'=$2d Z8`J)ԭX%X5VNܽL*Ŀ+PZgIzjxxwYt)_^&~!DIt­`ao-zo*pACXR..LBm EtV gݑHedXކam\mD)+9?LF>cѢE`矙8q"[l)"BI)Xwte{*/p,`k3hzXz*[N So}YFwFհԨ\aaFEEQfM~'ziݺ5۷/B˥_}u)P.*Paǂ ze+ŀڒ3Pl?TcŸKFw:U YɒƤ.S ٳ:`mmMZZ3d…bmm'|7oV[n-[ԩvvvT*BCCMq㰳ttڕׯ&227xҥKcgg;Cff3}F!(V.͟k;xWi 4Z«[^e>>l뾍I^)K/}'<ʛF;2tP6mʥK}saȐ! j|.d`` +-b֬Y&ұcGOٿ?_1Xbk֬>vZػw/:ubϞ=?(UW_}?~)?e.*3 ?_m&LBxqfT+] ٬guz~ βWor'qñjL˳` ?çyff&!!!L6-z>{8qbu:u"00N(UF 8|0:u"88 d7AHH/O!uy4Z4wU!ג17d.E@i k8jfAKq2cˠV. v(B4hfߜ;w:u`aiqqqzs'&&&<ڇ*Tx~r;N z222^NN6tyy!00%cbJ Xl*:ME֠8b:E{IJtyY,*5o| GPѺ"@gxq+9U?EJYvkgzy:fGq.54 MCʕMބD(=M?'}>XfL={(Usח|oQ%aJĔGI4ʐ==N!LΧ~Jtt4UV>V3SVVkze}E2dLLɘ1&1c\n uoP[>2s(ߓsq}|6!Bd]qMѽfw4jM;PCWX??gP #۹R4P}&MbxyyҥKdȑ 8GGǏm۶|t֍m۶w^:τ"##y&@vʕcȐ!Liժ6lcƌq @۷3x޽{0sLΝ+j*4㿠4 Guзo_{Hٓ=?cimm\F ~G!˰/$v殪l'niTԷ_e ̎r)_ʒA. jBEr)v Ba>]b;~׀~?B:.:|0xek0k/;\Aα,;ƚzfh7z"/+Řx_gbJ;a =VlSz]ʗ/O``4BQ%qT>-{\TrI(®]|}kn<*y0k*^^Vޠl qFn1݃(y,ZdΟ?e˖ѽ{w>^^^L2@Ba/]2k5v?֭ eٜ; }){7kn.&<]gˉ,=bmw"44mdCVKJJJ%)E!5lFphrv@Dݍ"0$=`caÐCX 6䬼"pn}2rb|߮](„BFH$+=r)gc ogrRy& C8t76G=B!= `![R>B.$\#='쓯'Db靈u(+}^!Q."ϙ|֭KZZ9jB0~?8ظl 64歫 O gNEeY7Nz}_rD\ K^ǐQ.5aD|f.\iӦӠA,-sN"epǸ8䶜CQ.K3;ݚ8J(Lps!%KJ׏-ƽ]Uw! &Ӯzա&o ^ƋYtjə"='SB޿(\NR^^߮1ʥD/˻?744N:QL썬pqq᭷* (9t}Tm }6mUsWU$(Q3"FP|MxMc޿1 ]u Z7ƈvnԯ&WDɑ~燵uW!%=WUiUD?G:@E늌k:5c~Y~:y²\,U k0]MjT(QT 405QFT&E^L[NZc޺杘ǎ;j ȐC(m|,1F$(~-pL̻Q4/_9ߟ"AB$4*5t-GB/Uʷgeդn3xJ;<׾e Ȏr2E7VsBi& ?TZUnBG|!KYfLm>v kQ ,=pMǣrFu[jh-gdu:}!`P,YB.]hҤ 7Y,\WWW~xxxj`֭9^W3gRZ5lllh߾=Ν~}߾}3رcDDDݻ3 !J'a+毌= )Ͼ|pp*sYys5o&ǚeSydv6wnF&g=<<˳lܸ &pBZn\?> ?Glݺ___:D-/3gVv|'tؑ/RlYZjEtttΘ1{c޽{_Aӧ~Y~=?|MV^=֭cĈXYYN;v/J*V!E{:+kX?Œ">-E+z,TۛFP޺3`Pu ^kTmh(Q.B< CJgIHHӦM˱d}L81ǺN:@xx8111dji׮fĈ&ܾ};qqqֵkWөU'NgϞO%Aol.1.{ëg }kϯee9r.ϴ,?`0rPUamp$q;B< ?ȳš뱷ϱޞ\on\v-}XN:M2e3g[FV}vXz5u?dddd/''u:]T|r)S6&l/X0==Q=0n~V6[r%*Uk׮Yo˖-Y|#_>}:&M^NNN 7t:ر"Sy>&aq(P|ыϿߓn0/tAz _Tb*yO ]grqTam\ڸZ &?;9$3i۷oog֞@+++<== G냂֭[&(((ٹ={ЪU+\]]qpp ((M333ٿ?_|E})ʕ+8p}O<7VV5Yoiio?4HÔ<{Ayj yS$93姗 :TQcrOY?OG7(4^QF3zg'4%s>IcYqIf̘~4i ooo.]Jdd$#GcӶm[ uƶmػw/ ϨUj>TR7DZw2dI]WҒMVٱc3i"%Afþ\ڀ` AǏ~dQ"3_ڒ)^SSSKQ'h.>riSˎQv(!IX;j8q"!!!Ou???裏Aܹggg"##Qoժ6lcƌq @w}4FMbb"-Z`Ϟ=-[6DZWXAVW^}'\v FCڵoyk o\n:^V歫PUW'V΍^i5Aa߷X?\^mX%E`>Jʕx3dь=:ggϞcQT̜93g>֭{k bРA} ?[Ǻ61wUń>>#G&cx[X2 l WkP˳:J'9E!::?'}bujRܕ]lj, N.+?`d6JYddh$+\tƿ UZGBү3ovDQRH((ZZA! *6\v7Y9g @GLl6'[\sO~š#QI}vkW(!J"iEGZ"4.b\]烵y*|}kbRbhPSO};^OLe06Аi sRhΛ$EL@!Dp=~H%NA%b['}l6gPځ뫨Uj/e0IATԯVkѹD!BvGÞ`A 55we.*9't(Rh~c ,oErHm&iU.(ܤB^iw`q^Ww5tM}r;tOQ72a.\H֭Yd ]tԨQd`ѣ[nחCѢE K̙êU]6| ;vŋ-[6{_}{w?p3LFvYd$PDKТ*ZUTk)Uꪭʥ)jۅ- UtUJ,-v EHm2s~ SӉd_//<|L<4ȲFN:QR%6nŋׯJ)>bo7n iUڽvb޹w* ݻ`$rCͫ{z>[R0~xu… oa (_$''s)߿?>4B 9-yz[6*nwҶAG=]o2p0ȕ\R&$%%LFEEP15ʪCBZZQQQz֭[`N>I&QjU^xz-, $7ΓORRm۶[~~>l brn}XI!]+gϞ\x'r9Yby1fڢE -ZĄ xwQ/0frss:t(4k֌Xzŋ{Opp0 b̘16Z-+Ce˖8;;ӻwo>eSQ}Ze+o>ˏ.Pr4m5/٢@ hBd YO!(@СCܷn:ݻӽ{[h&::M4awWj_XOro( kհ] ,9y/}o)a(pS#1zr;ңiU&ǵ{+e X{fȃU`VyxJѾ};'` pE W|+(^oW㦗8!(|: a. ڲb33(~K1{m^kGljteoBQV'h2i8\ov+J)6=sq1dEsk*2 V+N !C@@!!b6qny}7Ǖ0H8ˁ9\jIYA‹US)V!> a/.7O1o?&g5 |g;cg)$^2qԖu!DI+;B gooJW,;y Pe\:7 ╖4Y=BA@!ʳ|[瘷Ev\. Eo\<!k (󔮏rjyT( !D" եX2om')ߋXm;sZ3Uu{$c^Z!V@%?,psvTiki?oO%'Q"*,hC!$B'/yj3%xV)n:|͊]9_nf KQ!$B'`+pvyph/JK.`ʺe$pjCnן"$CBQW/P˂ Pcug˙LZ~Yʛ#<^B" 1 oG|XÛ- Ɔ8j<ЛO6F%B[ 񰺜G&9nގOEdd޶5|02upsɧ_d0jsMC !% +pa^yY h >S]V,ٚA5?  ߫hA+ӼBQ&I(hѐ  ӥ:n|R٬+2 ?R?$w:yHO? !7 | No6?R3ivסУuJ0Ť]"Bq$aph%, y'tfԊzZI]Pp@F&o,\B!2o5W vڬ\_&MǸTx畖Y_tB# eUi~bn:Nvcɺ}+BpBF@1Fi aou !N@!ʢñ)K.@Xb9ɤXKf[ʵZi>'BQH(DYb4aSy;Cr Y ?KKM8q˼ݺ+M> !(} QVd>ټk543sXpoZ2:Tq;mѬ7}?s !([$,89A??RlMM)J>itTIFh+V`BQvI(Di2º)Cv#ק|L"g0,H8ٖr\|гq84xnB<$dBjyW!}UӯT| ̅t;񨴃=I߰us !xH(Di89?nӗ?9K<ϫq+Ck7^&z>B$B$c!O  篿˷hbՁtr8yo"/}^{.:0!;I()WS'7SofNqg4L|sƈn7\IÀR N[tޛ:ీGyhL^&H(Dq8 ?= Kj5m4 8y'⠻LʭՁk$B!!Ix.Uqmc!ֺJ)]M)9ny[K6F5t;R/3S Kh2zp\5hԹbxxU*!+%VĬY B DDDaÆ_t)aaazX|~L6mؿe'8p 8;;SF }] h4?+W|υUwo p䯠;~sIW@0TO0HbF7$BQ, ŋ9r$f͢e˖|<$''SZ5ٓI&ѵkW/_N=ظq#͚5`ƌ̜9 Pvm&OL9t>[R0~xu… oatؑ;Zڬ^:b6 ropߑW8?>KA _75ZI.« ^Ϡs\BbM HJJbرVQQQ$$$yLbb"F*С111FTTe^u$$$0x"*^ΝˣVZ5D9es_tz˩cGyY2.9#Y˼jWiWZmC!I&FIKK+򘴴ֿwQuNJ"E9w0Ď#קu]|xh?Y1|OV%c)i}x=UٷD"B܋bO{ŋ8q"Ν#<<+VlTKMM:עE -ZĄ xwQ/0frss:t(4k֌XãGrQTbuL`ɜ35f=9ېM)\+0'~^Y{,%0 ??OU{JrBPw P}~@Vղ |1/6p%7K+Z Қ~ԿmfB!*IEv]/d?Nv9+-ggw_~ԨX?B$|ʹ3`9B!6ē|  h}V=JﺽRF#BacZ+:BQl$հj<\8h C'-U ,zO%~8sa>u_Q!Q8:ȏBI~É]WPhbI)>(2q]sDJ9<?gÜμ9f;^TꏣWBQ$hR 1kqO:*GnT}]~'BQI(*cÚq_zq>?I˞|wԋ{4+8Sqo>ɭ B:I!͎}mgoxr<4ZGL&iL_\4k| ``×\!Pmn:`*D98>?xf8䳞 zA`BqIE49.J)V'eʸ9D&μh(uBJ@Q]iu_Sz0k*9+8h@Vz5īE[`v_!($eGBGVoO;0?vpMv3jx8yjB$f:It2m]zN+v!B@QzZϯ5S۹>K~@ER9c?B:o˃B!_$ (yJ;z=csdʬ FB[ C䊟Bq?$%X?Hk.~|l]TL g[8iVXQB! IE+;6 Se:h:G]X3z`AHS:?N Cu_!(o$q/4‘mhJV~CVP4 51K+Ku IDATB!D& x.O`7Pg.- ).lmVR2Hz^B;! xpNoM?\V9S,Ǎ6l#ehn9Y g)A !%E@qL&8gNNnvG>:ds\ɪ 4@h$CAIB& k `H.09aBغ PRZ#.N'B-,M^6$-ͳYsރ # v%}Z"kkT[ZBI>[f -5Fr-mtЃlZQ?B]!V$~<ͻg1k{A|J#p pAjO3t= T.BI(l); Gkߩr8`t;i`T<𪼣W!($0vKsvMѺ:b# +ծ !I( ,O1ҕ'M]αr"Bb.^{['W ^gܷ؂V^ 'V'S)@!$]n&lå,3HU,Ykк'јhۀi[-RB!$Uj7(W9p܆S͘耖A-` MR.B!D9& `y_ gspxzzI>}|U{Һuk\2'ND)EVil?f#[)]ψ~_hVXΘGh !.^#G2k,ZlO?Mrr2ժپE"11={2i$vѣ7nYf̘13g`j׮ɓi߾=ݼ]޽9}4+WekѧO~gi߾=m۶e۶m>|ʛoYaggvv\5$;5܎Rjw/ !L+p̙ 8W_}VZٳ:uMڷoϸq7nߢ"&&ӭ[7.\?| \͛7[ƹsɡCS_5yyy,X^Oxx8f̙=L<fH6{`cC..hZ]G iwO!wX;vUyTT EȨQ:t@LL )))eٯiݺ5 ,J eĖĖĖĚ=#''PJ䉀?O*n;z7TT ۸q=ze;;;U-s >R'22,nje_L !B'žѣ7o_~%`ԨQ2dky`Ĉ2}tK:u21zo>/_Δ)SB!ťٳ'/^dĉ;wpVXAp88hтE1ayjԨŋ3f :L5kFlle @Ç[ܹ3|e'qqqiӦxyy1zh{B!ʣydС :}֭)޽;ݻwe{hoYۛjРׯm!BD^'B!IB!$B!vF@!B;# B!P!H(Bag$B!3 !BؙyHy ;;m0!;;NHR'!!AiZ5eu5e6o\2nG/*%%E-YDK+VǫK*@-_jYcreSOŋDլY3aFy?1=2dUYݺuرcKGFzzT||RԢE,uΜ9ʕ+R<uKoVzRj̘1nݺV}5j媱=feiwӗd0ѣ)))YDܚ$eYѢER_bذa,[Pt:;w}Yɾ}8wNll,zK?+66MTL۵k޽{ٵkOӦMy饗,lfyÇ @hh(V}-(( >>*&YYYlݺRg˖-deeYY~r9bSTJ:&99988X4kZ20M_Jʍȑ#^q%񤉸{7/Trr9rruuU'N(ݓ_]yzzu֩sYX 2DURE^ZرC=E.yҮ];czjUJ"<5jJNNV_|-<)1)`/&[nUWGQ_rqqQ_}δiӔZlڻwz\aÆ*11Q%&& X-oqe^|Ew^l2aŦMVUӦMSPӦM+e`*WlYfٲeW3Rʕ+jΝjΝ P3gT;wST&M,Kc:YYYkO>ݻ-iԨ_~%իWGףbʕjg}cǎY qh4ڴicS >???*T@Vضme5khڴ)...hтCY޽ml߾qBM a.\+[laƌL8{n'::O>N:E=o_%..&Zp!lٲ>?̛7ϲW^aӦM,Z={ /бcG9bԩS7oϦogΜgGe̞=/ɓ')҉'RJΝ;g ,))=zЫW/Ktt4 , cƍǨQ,1UJѩS'XbIII4i҄vq%KGcҥڵk=m۶fڵ+& [zjΝ;Dzeˊǘ1cXt) .dǎԬY:X`|l߾GGG `K/QJmFRRcǎBRvu֪UVVe>zRJ(@ܹӲ?33SjڵJ)֮]zjKS*@;vR6x`աCs׫WOL&KoիRѣJѨ3gX]vjܸqJ)ϟk׮ێSչ>S榌FRJ?*88[oު쭷Raaa`ձcG:={TO?RJ5k(gUF?WJ)*No۟t{*z)T~T.]RJ]zUt:_[ 5c T__U*77W)Z`m'( vaÆVہW;Pzu?ۼys4e;22#G`4ٱcJ)j׮O||մDFFZe˖\zӧO8@˖-ZliYdd$W^ոRRRLJ9v{zxxxX|SSSz ǎ`0XCcYxq | GͫSO=ŴiӬ.xxC Bة?Oi4˔⍇)M ;hn0LhZjV,vvvJ슢scLw:n۹ǙL&ebŊZ*s%((Dxx84ss50Oݛ_~w}Eѵk׻@!WΝ;g){m޼fVZhZ7nh$==5kZ 󄅅`%$$Nʕ艹7Z%$$Pvm$qխ[&M͸nŋ9p&L]vԫWL:NNNVW#f͚899Y`0}vիwX]6F"66nݺ1{:^Q$BpvvyL6d֯_τ XNb:to?#F⥗^o߾,[mYb=gС:u7x?2z{Z27d͚5L4ÇpB>X۴i3f|,Y2zHyVZʼn'HHH`„ } ̙ѣG=zU???Yr%ϟ'++˦WWW^uz-V\Irr2 "''Urss6l֭ɓlڴm۶s)(}2,(җ_~ɀhڴ)uaƌDEE=Knn.=Z7x^{ͲL<7|3gCdd$<=rʬXzGyoooxl&M׿ŤI dĉߪޛoIRR|t0OX3`.\@@@O<<-bᄇSN>#R/|GL8_}r M6eժUxyyUZ-/^o߾?___u{wW !YBRHH#G,SB[)`!B;# B!)`!B;#WB!$B!vF@!B;# B!P!H(Bag$B!3 !BIB!;>׆*AIENDB`cykhash-2.0.0/doc/imgs/set_vs_pyobjectset.png000066400000000000000000001162101414255425400213020ustar00rootroot00000000000000PNG  IHDR5sBIT|d pHYsaa?i9tEXtSoftwarematplotlib version 3.0.3, http://matplotlib.org/ IDATx{\LT45tZH"M=amh)B~صܾm..D KJ[$)25a;?c*S.C~>`sd>s>c B!CKB!w @B! C !BB!DPH!a$B0TB!h* !B4 B! @B! C !BB!DPH!a$B0TB!h* !B4 B! @B! C !BB!DPH!a$B0TB!h* !B4 B! @B! C !BB!DPH!a$B0TB!h* !B4 B! @B! C !BB!DPHyk$ vx~+q $$oeZoCJJ BBBP!*v8<>쳷,]TېKRHBGB@ '|0!DcP ! \HHx<~w 2hԨ q&Lccc(Gpqqޗa….5k@*++W~PVV .~W888W^üj:t'O`ȑ#6 wKKKX 8}tݷo_#**[vq?~B+,XD|nmwmܼysAZZZ΍^T!ܲB"p좣3rm󡣣Íg`nnⲗ۾xJWxB,//VVV*>ƌ۷޽{:t(LMMѱcGsm}۷oȑ#X`ٳĂ СC߿?wX [[[ٳ'ѤIH$W- B@nn.VXF?">>:u舲2dff"66[lU?~<&M:uR;hݺ5;wã֭[ѷo_x{{͚5Ǐ+W`޽VZmX ===KQ3uW099 0D"&ȑ#ك&11`}]KKK󙭭-‚M2(e}۷osqqaL"(VFGG+-?~Y=ؖ-[ڭ[1mmm]Bcj? !oYHH.]H pϞ=͛=2%%666J8Tʽχ 1b1 ;;/@\\z >F!|(((((|ښD \f &LupqcŊJ퍍޽u*.P__ Yʇ2ʉ2ʉ2ʉ2ʉ"M·&jR)ѻwo{FRRJĈ#```6B$Byo4GA&Larss_o?+//Gyy9=UTT#UMCEPF9QF9QF9QF9QФsI(ܕS{722ZBjmb ,]Tiy\\~?TeeeeEuv lҤ z>|+޽<… 1k,} ޽{81LLVTVV")) :uN1ʇ2ʉAGG :@||<4n|WM('41UW4YEvs1pZAyy9WG @ (-HR1#''GV|((ONx< rtUӿSrH)YQY0foضm0ydرcѬY3;###1h xr9222 KKKKZ."Hs'|CDY]sC^^߿-Z4@ByQ)|"''ZBll,lmmYYYJ_7T*\.urHRї;(ա(ON6mLTTTPH SbԩծKLLTZPqyA_̄9!DQuB!a$*֭֭SsDáCrD.Ur~!.]';ג};}ӟQPB!A$FӦMv DSRjus@ L.ҿUٳg;v,D",,,垚@ ӧO֕c޼y@ @-Gd2L0vvv pttAテ3<{ î]t/eff޽;sݻpqqAll,>%%H$ƌGqu놠 ̚5 M4$ `xڄM6غu+lmmaii ___ {ff&N<}axae_5̙W#GDee% 008s _+Wr瑓]M6|2;Wa;v쀎Ν;[ҥK(p_ۈ?{ڵk tعs'׾_#F EpիWuָr .\3g(>3"66pwwGϞ=cѣG1d|gߑ8+++nz<ׯ_pV)DŽ*s(OKu_;Fꭰ`JJKKYJJ +--ɞ=c)N%{Lsz)ew3Pf̘c֖]1XXXspp`RTi_iii WSNeCޏ7RVPPd2 .pRSS.W<c,##`?ƍ KMMe1BBBעEX޽egg3,--1X׮]Y6mjCK,a,;;d2VPP=ʴXNNc+W2gggnC1HĊc5yyy)wܹe˖{[[[֧O6Çg}UhSfhh裏֭[cyxxѣGxn/J]~=[׮]mM]mHR);tPr}Gyv6K Kqtbw~*>}Ǩ[SP QpHRxxxpˌXmaÆ^WB[[]vx[lAѴiSD"DDD ++KxTpGƍ뜫nݚpӧO%K?&''ԩSDܫR;wv/:lll`eeŽ\.GZZ罣oƅ ۷o/ <ϟ>===q-d2Ɣb(!##իWѳg:k]޲eKy'|8*<@ߗ|a 1B7C<W2\Oa(d$ bøqp5$%%!88 Sx{{QFX|R5{l$$$`ٲeHOOǎ;qF̙3Gݹsj*cӦMػw/f̘QmLz Ǐ#33III׿˗/,Y~ K,Ajj*_UV)̙3믿; ./^Dff&=zBdOh&j;t; @dҥ |||ЫW/tڵmƍOOOȑ#011?ԩS䄀<{ 0yd 2ÇGǎ_㳜_kkktC y[d2>}7oXZZܹsdFV0c 4j蕽aaa5ڶmR,͛7ǐ!Cп :...\,ULc*swwGLL vލVZa Bٳg#99m۶ŲeoojcxE.]0~x888`Ĉ̄SݻF6mУGG}1X׼Ϝ9\̙mmmlM6U_JQ/TӦhŰ{{ux5Baa! ֕!##vvvR֋_f\HH:W2'x7uΛfaae˖>9y]EEbccѯ_?qrH]`kLl"~]}k HHSXXK.a׮]KJJp9dIcܶmF`b!Ucx|F`@sHtرuXTTTT b!!!Xޤ`cBPテGu$ X,X,Vwbkk!yumрjHs%`B!uwBڵӹsa4[ @B!UOƒeMNɄjPH!xr`4v L@ !Bޒ_sr9  sIPH!7eܟ6 OXo'ATҭ[7nDu֩9"euСCo9"BLD`ee0VO[[aPHҥK8qx-999۷xZB۷gcX_"/yI5mTmǖJoyo JХ ! 4;Y_n ph5gl7H<{ cǎCU@ IDATH$ֿ܋XZZbܺr̛7hѢ"##2 &LB!~zcaРA '''Ç1`B٩*/eff޽;sݻpqqAll,>%%H$ƌGqu놠 ̚5 M4$ `x\v ݻwX,Fƍѭ[7\|[.]@(ӧOdzgϸݻ3g@lBUDe^-ZfVh C1VV.::zABaΝSN077W_}diF}vZ޽...ŵk׸cǎa!##+r9&M )) 'N|}Єb8p1Cvv6N< ]]]L>>o_5{h_5F۷oCGGJ8s H]" k֬Aii)ϟ___~/ǣ⯿Hh7nH-|J+KQ6]u|}#22ybeeUm,W^A鈉A||,]...}6CU `̙ b1V^sTuK/[.퍣G"..+V@XXM\`ʕJ*$; QpQ"$$?# \I&)bcc&q?(-CCD"*x*_ԩ>_ h޼9|>.\ HOO'O(>>> _WWWr>}gϢSN:u*8;;/_.7ɓ'9eY[[cɘ t (D0aΝFdd$Oܽ{;wP(-$ ƍСC@bb"bbb>>ի:wvU۶qƈ'Zn9r&&& cԩprrB@@7-ɓ1d >;vD~~Bo`m`mm]bȐ!8q"LMML!gggؼy3ΝL&7Zj3fQF C||<ѶmZjkk#??cǎF^z!$$1Oƭ[駟m۶XhePdff⣏>R|c (:rAu`'uQF(,,ĬRVV kb\"K+ʇ2ʉuWwE~ƹj2ʉ"UC^XxK* طo^:$BބqTruʕik;,5p͚50a֭+V(߾};?~$E!D#?gϠ߱#[ {~QJR$''c {j9|0<<<M6ŨQ0|h?rspJUF @ (-Jd2x\]]O?)տ̜9{/HbŰm۸RAAA4?D<۷/B!w^n}=M~~>N< (((رcadd}}}nR:֡C===xyy!;;[ԫvkhӦ BCC.]>y'NЪU+/HLLė_~B.!!!OԢE 9?VZ{{{Ba߾}Lt`dd?zJ!gȞ4Ҵ4TD" k v|>d))),88Lccƌa ,gee1HĂXZZ_)[|,,,dXaaҺRJKK\XyJ/Yi+xM/N7o322bٳ,""={YZZ!Cׯfggƍm;n8fhh&ORSSّ#G>۶mc|feeBCCYNNa1v}zj￳;w 60mmmvc2yzzӧsDzelӦM֭[lŊLKK2[z5fgΜaٳT) `iii_f)))1vŌXYYD"aÜٙ3gիW7k޼9J1Ƣg۷gIIIˬCSN>XFǎc,::ݺu8pI$O>a...,..ݹs9rƲrn:fhhӧҥKL[[,33]r_;W_}Ŝرc؝;wXTT,11UVV3,--'OA&&TަD*Cq;B9a1YYc)NXlGmߚ1i&fkktuu;;}4k׮ c%%%;2@7|*++U>^ bƖWyTTTPZm6fddĊGeZZZ,771Uaùlڵ_~lٌ /{/˙) g16m4֣G l :vȦLc={p۴ib ;wѣGL(W2Xjj*.^ȵyOٷ~夠رYXX0;~8biii՞c3CCCVTTԾ鱤$&L`#Gd1v)T{w @eT(ȥR5e⯭;+|YA c b `SbԩծKLLTZ .>,(//GϞ=]0&riiixK~zǕdg_ܔ;5Һuk<x!痣>}ݻW^|<_|۷W^ŵkp!s;rۛ2oߞ{ƍ#55:tP)99.].sWVV\zVVVpppP<`kk {{{}KaTm۪| Bd{W(>y<Vm_ݡwA=>*5(zb뿺?BaX-wU<saڵXn\]]a``WNS۱ݑ_'N/zō_mڴ}vٓL3˪au9KbȐ!(..H$jD,ʕ+HLLD\\/^\tѣGѬY3!b!wi(~A`б͉M  5Pׯ[^uFE HHHPZײeK\zϞ=㖝;wZZZuqՅL&SXvY 8_|`oo_ uehhÇ#""{U .ɉ{#""?#Əϭkٲ%*++qEnY~~>-˗iiixq^44o͛7=w---nGzzzW{yOd^j*ɓ'ѲeKdeeqǩzY[[sP~ !Z'11^qnzzz?>͛]]]xzz"//7nѣd7!!!ôi0f̘۩D"3g0b4i͛7###Y R]]hӦ w^-oѢlݺb ,@f0p@ǴiӰa|O>/,^5ܽ{7nѵkWtCŚ5kмysܼy<}D"Aqq1}}}nqvHTKbܹsErr2lVtwwǽ{rʷBHU|4;s4ʓC=D(ѭ[Wy]yyyoup |b**B޸?)S!+ B !,-Y!/)'ٺiM$B>pLdMya!nn޴ZR * !X=zm+NB#d}9@6?@Q#uE>TB! ٓ'?{H蘘;,|03g; BQ;Y3dMth7m[X;,s֭v;wDhh[͊Fƍ!~` ʮF` N*$ ###XEe`" @D.cʕh޼9lll7_=z@('NDqq1 011A`` ***<ƽ{0sLx]aCf```;"11/|B(NLO p]]@ѓ@!J+d*(ʠ#BvpBDDD`ڵܹ3rrrpMO>Op%<| Btt4S`aaSN>|8ڴi8pnnn8q"mЮ];̟?8z(ƌ{{aaaXl+۷SLA.] 6È ߷o֮]ݻwv/Dff&v KKK3$$$ ֆX,9wf͚aΜ9iӦرcػwo`~0uTvZ$&& YYYhѢ:w [deezχ :ts~'ܿ9sرc·~F)!c ['{ZZhzD];,KDAjj*ѳgj׹qxzzB.s]=iaaz\Lo[ 9X IDATD"␕Uv[^UtUW^#OOÆ Cii)p0HĽN>;wBG6e`ا#" CB6RBUj+)Ć7r XBac5^J~q9gOxÇ>}066u;P 00NNN~:ڶm LO?U! bbpJ@0;F @@OOǼy󠫫 OOOƍ=z4,Yq!$$yyy6mƌÍSD"3g0b4i͛7###YU]hӦ w^rhd2tعs'B!lmmabbѣGcر wQwg  $,₂`+PVb*-u+7Z*"Z"[ $!Lf$C$ If>/.>gs_ܟ9̙~ǻK߾}޽;Ŭ[:(yH)|Mr0ƏM7Y[(A|ANz!zͨQ%**wyprWsELƚ1c۷o礓N")))s\r ^x!:ub'>B,+//g۶mуc޶裡=Zi$L)6#0ܹ3;vӺ^?_&AQI˥=LK?Lj usDuG: -nEvJ"""4$DZ#gCq護O5`eI^fb&f͢pjHyqbDEDDKpWĥ[\H .GDDDп>+Pl9y $nqE"L\r%'Ofڴil޼!C0l0\'..{тUH(:ɝ;)ShuW$RWy1n8ƏO޽?>,Zu àSNu¿AaohmA"O{<6mԩS봧aÆ+..[n|>>ly` ***KOe^~Jzm#$$$XsIə:LQHV?  /ܳg)))|G 4(cK/u֠u>c{KQQO=k֬>.3}+VtOHDDڜᆪ /b(:9 Rii)^{-Y]%B*nذ}Q^y۟܆sgo`jj*yyyMzdee1tP] G}4&4&4&cLʶ|ƞ0ʉ":=>|`8FHLL 6^?!11NNNN\ ƹw}`ۍjw:KӜn44&4&4&jLʿR\MPa H<ϣ }.U=++!1M-[йs(QDDBPŏ7Qm2I ==0p@,YBvv6&M`̘10sLOy'LQQ ,`˖- i#}Xf ݺu ;;0qDrrr_~g?)H%{X*srp:ec,F AFFF{:|I|JDDBI7ov6ή]I[GvV%rLB@+.f焉T|=I{a9V%rEDD_VƮI7S۵#m2\V%r\EDD~;(Sl11>w^V%rEDD¬d]wS.YLgX] Qi8v-Ig&s.K)4M͜E_ v;]=AV%$EDD ]f>FСW$tEDD/zNx+"(""R?ǟ]wk,H)T+|urM7ax+i """=I#iV$lED$dG$O0.K(HX+um^/t#6M UWxfYCe4;ED$,y~'/.&r@.x euY"-B{ED$8`$OR{[deRKץ;pɽH]{Le(Qy {&ބ.[];iq ""|sˆ$ubܽ{[]ED$$~?{=, g!~V%*(H1M}|^O=he ""r?9+VaeLb/DZ@ )yKd /DZ@ V`ytnnN "" 7xČ0n^ ""]nhoDZ7@iӊ?w~?#"yT ð,VMPDDڬMum{t1æMDDDڤ/bM0ˉ>)sfcV%&(HS?q1QK 0\.i3EDMIcѷ/]-auY"m}TߏI]{Le9~S0Cnݚ"i * Ʊxw-ehDڤ̟?k&|LDDZ+ߡCdqtDqvhuY"mV@wtl/mHX󗖲IT| H{a9ΔiZ@p!z٨ .GDDDп>F?0>|xxum?H[<=.K \ɓ'3m46o̐!C6lG]oǎu] 2*0++ٝIɆ QQ-YLiY]HH4K曁s 4;vӶ͛Ǹq?~<{fhѢ|\wuL>{ED~?{pH],gmuY"!c ƍyg3gL2xXr׳tR3g2}ku6FVVVlxӘӘk+c$Fk4c1i)4V`9KСC?~v;fimE```}L VYH7٭}LZZ8G<ϣ4&$$3wdff2zh Yd L4 1cƐ̙3O>AA""ҼY 7x-W$Z<~q@ꫯ8Sq8^Qgƌݻ>}f͚wggg71EDe ~~s5["ҴZ<׏lٲQ ##eQ}U4]^/q +i6S+Vŗt3fy91^Hٳ1v -?|n \,ZDDھ/)!?'e:)_ExC""<;v=nB"<>-"DŽ>!""-›Cc})-Y=&D’4|ow\ݺy՗(""WXHxmѹ3i/,+AHPf/)aM[쉉t{a9.].K$)HWT[)۲[|ŕ2^vgIƏEEd1juY"Rh"233(((U;|+Kd~׭phgeuY"R駟fҥL6 {+0/DDxI8<5Dm۶ѯ_vMII2MܹSr%l̝CZ]أGl[oq[Pt1a,HD_Ww-Byy9i'ꫯ2sLy+KcpJW[\o{RZRRRxꩧ~gei""HVﱙ$v+oDX&L ӱcGKF*z->oČ +ư<HLL9'~~:s7aX]40??zדﯳU&""GS'$׿PiC, _=?ƍ#99YϢDDFe^7r^\ݻyV%"'xurXb>"" 78t&8:t,9A/͛7sꩧZYW\BĉTl݊=)n/sg&`9 `ΝV ""WT[(s-[[7&bn;ロ}{Gn7][Dd߾V%"Mm6+^DDj1MStDgV%"M瓈 i;{`2w1ouY"LZ< 6 믿~ԾW\qE U%"./GRk fpбcG`?("2_| IyHsk-""-ft3D%Xz_~v/lAE""hrǍäIW$"-x7RXX~!nF * ֯g=iQt.}H4\Hv"^_4.",J> ]~9zHO$Xr~aapEp.m6.'DD\g3#!梋أ6K, 5ݲe \r 111e.ݻ3rH+J Y[=&R $eG|KPuq5jV!"6<۷{xDGgv[]X7px<vEvvvj…у|`իW3`W^9#"*9gD|yy{&usآ.KD,dW}w; 6ipȱ\zʕL< 2x`/^̰aIKK ߾}{Mi7ocǎ\r%'DDZʼ<.]Je~>=I[<8Yp8x7ܹ } m޼y70|y-Z̙3_xuqK| "|x|)]H[ GV%"p˖-lڴN;턶xشiSNӞw>iuVfϞ`:.**zlVivOFcDD9gwƤppyGci\~}lrѿ1bD=+++1M9~""m8tD sD^ .ɶѣ0`dɒ%dgg3i$ƌCJJJ3gdtIx<֬Y/̢E&`&gSj5l<81CX]b?FokԨQ3c K>}Xf ݺu ;;[,))!##]vi0jԨ{2""{Y2}K-HDZ;K`}Ʊ\ ##z{u1m_D_y> @0bH[`W⯼8Qdi<1 4봟wy,_ܢDDZ͛y˭C/bZi, ۶msfDDDEN|Λ&a=h]xapi,Qs޽FDuq/*"s\.6;n |GH8Mر I](6ΒXPPu]GRR]ta~z!zsE$yssqX*srpt/kuY",9|s 7o3e~my뭷;EDڢʃ9nġwp:DseH$ ViceHRi[nzMOOXC}DDY_0ʈ2.pXrV@PDYU?t)l.eHSi&];Dqa,@ݗKcܷWH}~)XED\dwN-[]; PiBbvp$'r.KD@&/+cIvH{a9].KD$H{݄-6eDD("rJu7%~ID~e4HPD~?{xCkb8> Q,R9Ni(n'eDduY""?IPD8'0 ̚IEY]H(%K_Nx/"S9FV`yt.wW$"rlEDAko#tyƍ"c("HEYYF&-HDT\p!=z ""4wҥ 2vѮ];.b>VDڒ?bOU$70.KD丄L\r%'Ofڴil޼!C0l0{q5װ~z6nHZZ޽+֮t&vz+K쥗)"a(d͛7q1~xzIMMeѢEӟDFFg}6vK.nݺ\DZbM0ˉ>)sfcV%"rBV<6mbԩuٰaCQZZ} "p׋=Vn[1 cvH~ * ИuLcRW8G8=׆iVqCJJ }j]㥗^b֭?[nwy/z=}ŊDEEVǙOsqQ޵+&i[JKKk),,$..r,{kyBi:I{Μ9꫼{ ?"RSSIOOo%++Ct:tm#$XSS-^| _Q^r$$4q-C`qXݻc+.&A#Iv!OD!!GE~~>3f`޽Ӈ5kЭ[7պn… x<\}u?~%Kf|Z Hz/fqu""/d @FF.{߾}{$"Nƍ5u_=D$lTiHŶmΙKH%v\rY\HR+(`…\*TVNk! Y]%E$$^/_3y> .DD("4)?bmp|2K'":(HȨIy~{{$~; WpϝH E62/ /!7M7a<VGPD,E^~/)P߾3=,NDR64Msǻ{7}XC]-PDuS6/7k6e6HNc.J֬BOPDoN|^øqt{#N> "ҪKK_e0J2LN:IPDZ%Tٿ?SٷՉm "~)f΢pvJǻ"t ð:OPDZ Ν}Ck`&I=mqu""CPD,;t˯`z`ߐtm8:t<("1++)_ؿi|=hwSO:Х("(#rgϢ⻪uA{! tH3SU3=>[oFa8W'"EET17"qx"#-NDDCPD_RBҥxE̊ 0 G ;p&w<9 "rTGFutz/gaqu""r<EA%}fQ78x]^|1aX\/@ ٱ}sRubcIf]6DDD)H8? v;F[oѾ剈HQJ\I+( zw^W'""MMP$>fzD3dŕHsQ S}Ǿs(C $~~[ 42 3 _O͓Y]@0x8+-zq1C/]wDD%)8496ǻs'{|T3+7/\=zAW_1rHwa̟?+ie_~E1;wHJc/Q c!W\ɓ6m7ofȐ! 6zҳgOf͚ENZZݗ˞7O1n3n椷"2͛Ǹq?~<waѢE̜93˹ ԩS[V/+#r_YV@1s Ν-NDDZM6t6l`QU"-)z r=IeNgM}S<,&$`^^>:TOM"^/^f;G8S0I*G.t2K.03NiLiL ڐ5 ès4͠1sLOԾvZqjjU].K ~ Lz I0I0I]4V`}A{O}Gfff~QQzdee1tPNgn-x/.(xx0joS.RzӘӘQs/Dt\ߟ,Fh+lqݸvl4͹((XO-@I'qYcquK8N1 1+#\фDd 0d4icƌ!%%%`_{nlBLL zyԧdF͚M֭uÝ3O=DD 8j(1c{O>Yn_qֵC~qq.{v._^۶;g.`# ]s kX\E!222Ȩwّ{U;_A.Wv]s dh׮>&"")H[gz|U?a!1\@{ݳՉHPiLӤx{Ιgv'Lǩ3xʼnHQX֭5ҍ`oߞo'H"bʼU)z̲v;1\@ȫ9| BEDDH-}(k^wGvգ W$+p$%YXȉSgz<ZWQ`"a$\5~g""2%lZM;x0ٿ? #GwI:h}CDDBߡCU(@#)Éj=,PDD)J3~J?7Wq蝵U bKG^E/~Яx{RQxw zDȫr:XX5%=׭`jJ>L[L q5 #"o_}CDDšof+, Gg$tlV(""z(J+,7(\;1•fa""")OɆ Z͡,LjIE0* °ۭ-TDDS6g* O9Gw8ڵBCPZ-EAUlI%`%GU#8t}CDD)Jb&_] o(W/zG¥bJi.iR鯤WAGN}+SSSVAWSvG$k>. ia""4M*J<>φnK[Q뙘znwϜhሄ&@Qa#VQn r{ď1l6^/|JQRcT}m-+,)d_W~ _iu !IPZlJ>¿AeNN`$I=!*E$T;?3}-=7;mNv7. ]Mۑ?isb3l-;aBPiT|%6Pa,O嗓p"N?JE5}%zV^YNΡV"ZEk@}옃~6ƿ7_]+AP M 伹nX6RD$W$Wv[T4{+d9b5?+8wq6G{۱=q밡('_ZJ駟RQ^ᆱ܈ j "z ܧKo˼e|_=7lkz5G5w0Mzn곯8rGahnӅ%(13}>ʿ:6o| /_?6ou:~Oy,6~2x5p16lm. i* !%aKPųkW |1:˝]=xуuyViriBRYeY'!+mTų˩2r_yu}U++jnW7pF ָ=^.{ӷw_" w@LDj?ήCy?'z b ٭놙ځ,(TV;XnΑmu[}Ҵ=^Myؑ^/kvޗio( CgQ\i/@9Duу=h}b8iML:bW^GmWnsvv7"l.krޮ[mwvt_"8lDDZ#ax{ʿ7-fy գуWٹcb,m:2P50 Z or >ߒj:!:\-|uH{d մ9mzY.. pBΝ޽{93?>C iUx8餓xG1bD VeeTlJ_S75}=p`\>gTt=e-~vZ$sڑXQi 5DZ=ӬOہ?WfO2voguLik~mWznM7fz&~J_%vb=``َirxM5QݯfxMhYX^kߤ߷ U2pʕL< 2x`/^̰aIKK qFF#<ˆ#_o[>C~[ NiTdz};~ػWÏuVqս;i7U+ƚTy}KYl+??'^X~|0ceĎ _GKӏ? ."]gaȪ'g F}xmb}Փ﫚Tz?bB3Vojbf?5>s;ӉV}jyM 6[ہm4fjKqCF^,jϝm99D󑜜\=99z93gdAk׮%**8*rhm/P[j5LF]~M`3mjBYgv'w5ܨjf8k&ukjn Us5˫7x<Ó^Ͷ j3A"VبZXkQQ]MڵFۨ Dzv`RÌ#~ʱ1Yn#Mvܛ>=ƵB[VV%RK\HG2M搜}Gfff~QQ7.پCTT4ӅpbٰmvVuxi:\Y.kbT~W]= k zm6ߺ ֭y]]=w5ۨZl6wշ&ةUu z1.Smkoi``q$vX1c'L˖-òe˺{ /<c1 Kx c1>nc1$@c11cLbd1nc1$@c11cLbd1>@5"\r偯jڊ+W@T>7\}\}\}\}\1)[ǥ8p`/gcjiiUo+d$>t:\pdt+W`8<,--ڏ#>>>>ADhiiF4 g1,--%;&&&&bRT"Ͷ1cL¸d1yBBBBo'&\pMqMqMqMĸa1c1&12cI 71c c1ƘphlWWW{;KJJ¨Q`aa;;;̜9'Oܸq˗/-~bƌ033-"""&χLLL_/CiRRd21)֤/lllЯ_?1%%%<!!! Ek455a,X˗/b***SSS8::"11Q* /puu)ܐN'߿3f̀FL&?OѼ!;<wVEll, 333h4… 5ZM}"fPRRRHTҶmۨ"##̌Ν;۩Hpp0ر;FBO<]zUYt)9::RVVĉ駟v""joo'///8q"RVVi4 _~Im6R*C1Ě >"##q&gggZh:t);;Μ9#$''REE͙3ʕ+BԩSˋ h|ss3ܹsRSS‚? 1$iTUUEׯ'BA}ݣ)[lll믿jڳg;#ի)55_|!7w']˗/ɓit *,,1cƐhVv40GK)..2z0 ,RI)))BL]]QFF|322:!>#JEDDCc+4vXᱡմLYYY 4RIll,q^ӑZdadeeE>UVV/B@'N "-[]~]IJJ"FC:fϞMSN?88Ν{큐Zxhl֬YK/jr{cHN.CWM튊🺾^s| ؀SLO2Ճ @Պh%쵰^^^h4BLpp0nܸ!\",,,ԫWpp0> Vk5~L,]B>w˵vvvzkݳ&qG]X̛7P*FTT͛'WJ5̐ߝ\zׄbd21=NqQ8pﵫ}+eL&nk< ϟGdd$233abbt:booo?~[nEXXw\&=Σ{n|OSOQQQh4XpasK5!ߐjj1w\t:lٲE4'՚@bkk \FM=./_ prrj5$WZW&hڻ466BPƠjZRRF@P@P ??6mBjOOOјjkk VqE{=cuMVZ8̝;Æ Â b ᬱkҙ!;l؈Xv&nݞcTYYIQQQdffF555ZdeeEyyyT__/| 1K.%'''ΦR4iR< R&''.oyb ۷'XΟ&^MHPG:}4}'ԯ_?㏅d4yuyˏÇSaa!ҰaD|2Ӽy󨢢Rt{\.d^ … Q LZZRLLkBeeeTVVF/ h5w']VK>,999Qyy5ƍ}&ph6oLdllL#Gn8׎;k׮Qxx8Y[[)M>jkkE;wBBBԔ)<<\t"<&cccrqq[c5bMT*rww>@4h͚5VIRф Bs% % Pjjj=zyRTV)!!A-{졡CR$wwwJMM}8+WPdd$=dbbBnnnzj/^._?.\HDkR]]}>[vdD1ci@c11cLbd1nc1$@c11cLbd1ncT`` z; aɒ%L&Cyyyo,Z3g4cH 0XoΝ;777vJ=VSSWWWaĈ_Wc]1L&Q/j={Z-J|++ c/KIP`` """kkkj$$$555zC/_ L<@^^d2ooobҤIhll7|XZZb޼yhmm߿?lll/:Sֆ8:: cƌ ;wD_* Νr=z4T*v7/._dpqqcRSSSOAR7nͻ`ڵ?>͡hbd&M‘#G1}ܠR@DȀPӧٳ\]]ސd 7;;;iNN|}}ѯ_?7'Ob9'ÇX7Ƙa1ڵkp!lذ: xPPPcxw駟b޽kvBCaӦMx qAѣx1uT>}ZimmERR>C?~vvvzaڴi5j9[bXnHzQ3YII fϞs碢 xױsNQܟ' >xװb D444 ==%%%9r$? k9s9RSS?Q\\矇NQ__.Tڵ 4hEիWcƍ8|0 /^,̅ ())A\\}dbIN@@FEDDT]]MLojj"KDD$@gϞ^y Ãt:0KDDtdTWW'/((^{5""ڱc :Tt͛79uttoMw]g_R4j*;;;ԩSE1s̡_WDDCtuQ̓O>I߈h͚5T*466 ^DD .{^JJ>a4 mذݻеk׈‚vyc2&QÇ=vpp@cc}coo~M4vcǎL&@ii)C 𕟟/illUUUOtիǪ*?^46~x!UUUnEz*lllD/D={χ,--KٳgjEP*=z-0::o0yd$''rg=>C Id2pIև)Z=בdw];t:r9JJJ EsƮ+DskOznw׹[tppp ߛϘ1Ķm۠h其s>ogz+%c޽ofv.gczn;ǃ\.7:::؈Aju鉂QVPP 8::h 0dQվ#GDCC ޾vK.  D1 :yAXVÇ2dVXL̚5 ;vc@ƘSSS;ɨ?9s~-E1vvv055EFF.^fuꫯbժU@ee%~ߢ/rp5#//ΝQ\\10cK}/^ ___ :6l)SaaavF \˗cɒ%;n:\uuuM֣8::"==VO? kkk=nfG?o֮] $&&bѢE+Wo&,,,qFy)5==Wŋ?BVc„ 㱍xyyaСشipP(شiogRsrr2t:,Xb߾}0`@ q%ŋŬYov1!1 **ʠc _f1nc1$/3cI d1nc1$@c11cLbd1nc1$@c11cLbd1ϳ(7IENDB`cykhash-2.0.0/doc/imgs/set_vs_pyobjectset_fixed_hash.png000066400000000000000000001326001414255425400234650ustar00rootroot00000000000000PNG  IHDR5sBIT|d pHYsaa?i9tEXtSoftwarematplotlib version 3.0.3, http://matplotlib.org/ IDATxwXT700t"05vF(@XST$FWAH"k(HQ9RPg}g9>kg)"""0c1Q`1cu@c15 c1Ƙ1cLpc1f8d1S32cNc1 '1cj@c15 c1Ƙ1cLpc1f8d1S32cNc1 '1cj@c15 c1Ƙ1cLpc1f8d1S32cNc1 '1cj@c15 c1Ƙ1cLpc1f8d1S32cNc1 '1cj@c15 c1Ƙ1cLp5 {i$D"DDDJ?>}Jo[?j c 8d;KKK9s}>}zOޔ~Ԇׯ#44@ZH$h׮]}w12 H$!CO.;v,LLL2ut ...r~~>>sA[[5B@@?~\nߏ͛CGGXjo޼#FaÆH$pvv?PǏcڴiD"AÆ ѧOܸqIII033B$A$׷!ڵkL}}}899/P)ڰChh(cN?ӡ9sv D"!O>agg۷o߅ 0`@GG-[Į]6lk׮±֔>c5 ;m޼yr9͘1>L˖-#===jٲ%ѕ+W5@?)J$---3gEEE/ԗ/+˩QFdkkK6mH9r$%K khhHeiӦ岳Ņhtaڻw/M2=J;v,9sΜ9CnݪRoNhEGuQPPP&55lllH.ȑ#`H$KDT~ |HDDǏ']]]Zl;v~W?OV=JΝ;'___ c{Ab{2+MUo۶Џ?(ܹ3hBĉrrr$f*vIhÆ :\N"._RgϞd``@OFqq1@,B"kk*Š&FM6Ν;:t(6lm"::Z(s}v؁L/=z44552zzz ō7kٳ߿ul?ZhQccգfծuVG]v%t&l2=IeWz`=͍ *=scbb*,Mh̙UU t!""#+++ݏmR6mhnܸ}NJWՋ5i҄JWV{dLM۷ZZZٳ']9s ^^^*匌0zh]r(RϞ=Yf!;;:tye˖5jJy+++ 0!!!ď?h,Zwʕر#>L8 999u<(8uTܹٳѦMׯv }}}r޽;LLLРA(R:t%ҰpB̟?h߾=|$%%!22֭u1f?CUFdm۶ׯ7occca֭4Uoݻ7<==FѣGåK{n{ذa;; Gyc3PX*x"ߟd2K+w{^^͚5r9b'RffJ9\N}={ ikkB(3zX:Qf1cQF$̌ڷoO_JL2e ڒX, R߾}UFʎ9B-[$DBǧJۼy3uڕI[[ˋOrDvvv$ĄZnM_~%R?H*{"ٳg;D"!{{{ nSw X,& ֭܊+Ύ455ˌ2,""z?c,$$HOO/s~^EMk"%%NFp\-ZUzv0cWS1ٳg5k`u;v VZz{18d=ܯ~믿6{ ;;;ʪN,Uhhh@Co{0cL-%%%ή2CHHHt12ԒΟ?21.@c15'0cNc1  J%ݻ}}}Dc1ƪ+++ҟWp=w7c1V)))n N_>g ZwQQB,ֺF8&eqL☔1QΆ=8|Ӿ@m~!+(cRǤ,IYUu>}K='c1'1cj@c152"Bqq1JJJ_QQ_}E8&e$&b3{pX OV{_"RRR$R8&e$&"֐d;{spXKJ% +++hkkWKZT"772LmoR}@&FÇ]tA`` >34h={B <"HXLHHZhC. ^^^x1ĉHKKSoڴiԩw^@"@Py ''#FL&lsqơaÆ000@np}~CGG 4!CܹsD…ՍѣgXrPgRRKc{3u 09NOVWZ1c;#** xbeك˗cy&8WWWaѣcZ qqqXnp]R kkkڵ ׯ_ܹs_`׮]*m ../ٗ{RR=={`͚5xAE_~%O˗/iӦ>|8((('pU,ZH8Tt-Z R©S~z?Taenݺ]vݻq:u{{{lݺU(_\\~)ŋW"$$sADDJ;K,Aq%|Fttt}""iiiŋѪU+t=:tC A߾} &&}Gjj*RSSk\r%<<79c{RՍ;wT޷Ν; ۊ.),,;8&޴xR U3gիpuuRɓh߾=&M-[J#7(..ƅ u…&L}0m4Zjk׮APAO,UErr2ݻ',9shڴ;vѸqcf͚RӧѴiSG=(].|QV--21hРg#111vn텸WT'c͡,,?Ӧ _Ù3a>k&'쭈Ν;1uT|?we K9s5j\QF˫R^SSSU.TW2 cǎŌ3 BFDD`ƍ믿c֭JP(1cp$&&"66V.\Ç9sTBGGG 8w.^??0UԩSqa$&&ҥK8z(|8FTTƌCP &&iii̬R_ttt+WӘ:u*Tnm CCC|EM,X ؼy3V^ӧ;u/^ؽ{7LRnz 4FRRN>JH͛۷c޼yիWxbX8qpuM(]PܹsHJJÇ]؛$')~w@,Ւ%0wd^@6mЄ T999ٳ-UvOOO㏅e8p+TG %%%ǚɡO>tuuܜ/^r2'߿ڶmKGڵ#GuQpp0YZZ6988ЦM(??|}}Аhĉ4{lrss/}^Gjj*ۗ$ Җ-[ʽ"("?C؞IرcDDH7&DBfff4j(zP>!!LFFF$JɉNJJ*_~!"\~ϛ7h͚5deeE:::4x`zQs!MMMw^m{f͚X,&[[[Zdv\N%|VXQΦɓ'b#GRrrPf޽ԢE ֦ А!CmgΜ͛D"j'Rqq1=]v$J %&&_~8&+itщnjMOY|&۷Oe}PPuԩ}lllhٲe*-[F²5i҄ۻ8ϔ&D/߿baaAaaaVMs ᘨx߾M ]uG'Б]8$"z/y!JJJ`nnܼnK|޽1l0r$&&bΜ9֭.^DRn(((EEE(**R)[TTJeߓKPwgJT*+IVVΟ?m۶a=^O>ũSp}8;;QGM>',**R9]QӋG3G+H 2+ bVBںN~ߢ˻j^V[ރ;rp,\eGEEAWWWe,,,Š%rrrjﻨ*صk>rبs";;666a``R6??)))d5zv-!''v'o]t)wX,.^I"##+oT'Njg͚Yfj5Uߛ|HRtFpp.** ۷ ܷEaX\旦m_44jt:]uahhCCúkgggW)&1ЀH$*]_MpLTV<XW eh(DZ$gaԨQpww6l؀dL06j .L2:u¢E0p@8rp\`СDRR 4h@%d1XQq1BӉ`3[o"FFF{۷o;v૯œ9sиqcܹm۶h!DZ IDATjjիزe ?~ KKKt;w~#c1.Qiӑ{( b^bz+@4i&MT?Gᣏ>*T*Ç_gc1Lܝ8 y/C àg{[2cW?H¿l֮n-Nc1Z#вmH4nr𥃬Jt邩Sx|+VsʪND"8P=b1pg'(NOI(vl cܸqݍW޽{w7<}Sw-ߐen.t!#-V NYyI]y<¢GՅu1Vmي>**fp= +ɓ'=z4d2,--tR/"VVV `̙D"A&MqFn=vXA*+WTi w}ggg899xdC*۶m1>?򖔔H}k׮Յ#ܹzzzpqqAddӧd21j(<|PޥK>C гgO( !\r]v>ХK\pA~it R666 “'O>ܹsD|.+! h|4kVu|H""URD^q^)R-ig̘cǎa_|/Eeٳ˗/ǎ;₴4\rE>zh9sVHTvB pi7ꈉ>===Ô=z ƒj"_~M4_~Ç֭[B@@ q dM'wXl0k,xyyѣB7oĉq)LMMѰaCW^|iG-[bڵD8spGW ,ƍ@"<<탛ƍWcLQa!}90 8- `+C۟yFjSظq#lق޳iͰ.|rr2,,,УGbڢM6ڵ ѣ^W,#44TXӧk.POOaaaχoٳͽ7nggjDӧo߾P֭[prrBrr2 WW2DZvZjJxF/lڴ 666HHH@ӦMXxqv`Qse1c 899AT\x%K0bb&M`ժUܹ3֮] hjjB__1؋Jr)S)@S h?EmSL۷QXXa -?l0ؿ?/_&:w\a{֭; d2⠥wwwajt̥7o.\LQŠ |С͛?S({E;v 2LxNU߾}[(|k>zE!11Q*RT)c5U!}|)Rج][GTKs#URDNN_pUQ궱A|||t=*sGaQQQ3fK-B!33Ҳyyy Dll,ܹSN?1k,9s|2n޼_~'OViĉTRf<;>(y:͚Ac;'NYK,AN0`;vD hdd0t͛7GLL <SSS.裏0i$899_-Ʉ 0dx{{m۶P Lxx8lllйsg 2ƍCÆ _OQRR8;;W^pttĚ5kVVV8uJJJ{SLKGo.]hؠe˖DFFFM?F=9Ǐ͛7e˖3g4󑔔ƍՂ{=)'>^ݲZ wk 044DVVp%f|$&&:::ծ[T";;u:x1)&1y7]QQ"##ѧO\DUy "d˖ 7_Cyoʾc1 MƎAi ?4)nwζmTnťW) ^ݧ0Xu) gӄ01ww΀DMDQQQ71uR{S ŰZ nZ {C__Q#rSCZYYnAC&kWObu@cL޾ ֠0+ Zff f]<c.h V( ߾?5 c1rAcF-nu#<c;6TBKgFF-Vx1SDU (0,/#7xfU {kݍ&""F/*ec*sᚵ?"- TG:P(bŊjuV̟?zFBBk6XD"<~{}yy8Y{ @D'[{JJ [83w'LDޕ+I$hl)wnz# REoW^En Jajjq!77W KKK" @xE.]pC$ }fdd`ᰶ.\]]}v~8P(b̘1ׇ-6l l/,,D`` ,--B V)"k׮E޽!Jaggݻw ۻu@}222 HpQ@ff&F cccw޸yf8MBGG={DJJuE-0| ?~q:::x믿"66~)؇֬Y&M@GG裏/=R)ܰg@RRv Y.[3j_pg] CC؆sǞ!VcYYYlˣׯS^^T* r*˦PI^vTVfΜIAnݢ'ORXX=y򄬬hȐ!tU!;;;!0aIWW6l@DDdmmMϧTJMM%"wҒ%K?۷oӪUHSSΞ=KDD%%%ԡC ڒdbbB?ݼy.\HGDDK,!:q%%%ɓ'駟~R )Q||<}WIׯ_'"m۶1 \ )ٙN8A/_&OOOrppB"" 'XLtipiӆڷo/NNA7oޤ}B!Nڵ#۷o)22 hŊd`` >''Ο?OO?QRR]tV\)_tm 'DBT\\L{%O*Ÿ6Pff&TyrW!ts'&yׯS|ǎtщv[-.x^eWP hAݿ r|L$H(,,̶ 61_}" JKK#g \.b̰a[X|O>4m4"8OeRI 6kɓ[nBBVh„ *ڶmK'N$"|211;w [h!$b N:%l!IRڵk=KI.Q\\s eO?odff͛Ғ>L_qX޽{˔%:}cÉرc233m.qX:~:$Ѫ5]wtPGG"Nx CAA3E777 :tRxa 455eKKK} ~:ѳgOd2eܾ}m0N֡CH'}} y}wa"$WJ990ׇ+b*"׋2۔Je.]˗/NJ+ ===L:WY[ZBbb"~79r^^^ѣpZM<~~~hѢ޽M6{Ū<ŰVgRP 2JdЀN.ǥK(̝;!!!8CQ#'H$j]x"~^Z4wGHhU%֭^^ո̿I&J)Yf|2 &c=Xx1gL6 s΅3x}|G޽;V^]ϟ$4nfffBZ't 4蕎C&aѢEpwwᅬ$DFFVyJ=44;v@ͱyfl۶ ͚5S)3|phiiaĈQ֭[_~!22ReZWWfˆ#T;vT'OOO믈F۶mѳgOXBz{ѬY3̜9Sk߾=&Loooa022¾}Э[78;;cݺuؾ};\\\ ,ܹspB8;;QF ٳann^8S %ZZZL8c/%NVb/ CCCdee@e[~~>aggW&1 Rl9H$_@Phժk`ܽ{6UꛮӧOsfջt$qzJ>XzޕxTGeꂧ"bh׮]$)))FcTAb"Rǡ]hfzH]߫n0m6[Ǐ`P^t{6AԴB2}}cU:w.PRar41?c c1#"d_+vpc?m` {e2co eAMh@$_d'-1{Ôde!% y.B$jbUbNc7HQj*RƍC[А`k[!) {k 57o"(y Z BGNX9B+VT{[bУ+""FFF [ H ߇vPG{Gq|>;);* cB i˖Plb+{qP*Xh H`kkopUt R7nrss}}}}1h |)PTT4ܹg1(AFFkkk۷oW׋S ~-ƌ}}}bÆ B:::P(Xpab[[[H$XYY!((Hޙ3gQFC۶m ŧ~,BBB|ƘyOgTPa!dݻ6|4y2JTVT"Zx<]*֬=>saر#RSSq <}zBvpyZnYf¨Q`oo_c`ҥX` ٳ'NDNUV_~]`kkك˗cǎpqqAZZ\"lO;v G^pUo+VܹsdU=cL}WD#ooX "-jf?eu(vvꜜ\Wqر#-[@^ǢE`nnTի '''111 455 Faɓcݕ&}If '''$''I&ر#D"ryb bhIطoq]X;=3}tǷ~ CCCD"cQQR k>@h0q"f*PPPݻMHCP*h@SSSXă*m| 7oSSSd2DEE!997o.\t˗/AAA5l0ؿ?.]iӦd}vg7ӧH xihb|MS<XbM\YJ997-SU.+V*O)T*+mwҥX|9VXWWWaԩ(,,tjժoȑ#B=gϞJ봱A||# =IIR 0 @@@pUl%%%x>*c w@6AڢE}w)N ̚5 3g΄6:tt\v #Gļyバc5jp_U( 8q1$ 4hݻO1-[WJ/_KKKhؽ{7,,,^zm۶֭[!J!ajj#GbXt)ZlѣpuuE>}P(AWW5>/5! n152̙iӦaܹpvv7|=ᅬ>ݻwիU󑔔ƍLhUVD.]`aaAqd2,Zxȗ!,, :t@ѣGcڴipttĀp9ڷo &fffXx+c{GFÇ89A};';<2w2t =!A" ˄*ꋲR" bv16CW*bKՄ.N yDcduyΓ30~s *-[T핿oʾ}n͛@N Tnzm'M;Sz@ήB%..N),,h4*W^UFc|T&sRYMv?]II}vDRꍺ;NJ\qaJiNُy=rŜܴi/fѣW%yt(-\Vf$ K,駟f„ I@@˖-rHCxx8;v$<|3gV۷/?S9x`ۆׯB4LzMV)...`(5EQ0L7]*oM| 0#GT3Ng1-j2'& EQ0 in(kf9QJJ<{6y_d8JFX{Ě^XDh4Vۛ*z.\mHDDͫ4]*O[[[|||˻Zv7[6D5^^^ٵ`o)=R٭III ۷|(&&Fڞ"t< Çqv㘋5G .Au" &wȑ#^}=<)rXX$ B!MHz4gϢ1j%DIB!nE)Z}Vj%De`BXH?ۼ<:v U(*dAsϡ߶->[OOBBMQ|1i.=ƙ{{EʄPB`)&ߊ(MǏ;-º'@!Dd*.&yLrwk LJa0TLIBssI| ~t:""pe QoHBѠ._&a$ϜٙfsHe QHB`?Oĉ&ʕ8]@! B$<lZ$U5kvYBKrBď);.ia?!n@Bvu$"Jq1.<@O?ŶI^BX$EQC2>!O;oYOOB:o>Y_|xE4ʕ a$ !(B={dH¢HBa1J^%;>}.K#P!E(IL"aDJ.\ՕeKqMHB{EN0q|J۶U,!,,#^!.S}vܰ^ŸIBz+g.&NĔSXe a$ !DE4(%к] !W+@ѣ*Vʄh8$ !7g&q4QxIBQ/I#@7i{z]VOB(>{(MMӓU+qQHBadL99صjEVW,!j56mh49rB4911$2 .]h|M]Oj5feeMUɓ'c4k!*ao &.=avYBGW}kB!T( G46 N#!ZdL[>776/BJi))s璽e+? ϣhTLq=O3!5f*( 3w.MF W,!M;}_}UӧӸqczɥKjϥKҪU+֭!(({{{ضm[tggg4iB޽9tPjBkSz*Ə'% a!z-o=x |-Ã_~i&^z%f͚ѣG߿?UnAF^z^!oߞ>_~вeKKzzz^BXD.Mhh4zaBT`BBm۶` :I&q3wUYd O?4&L 00HXlYGFFҧOر#<#DFFo3zhzM֭ fɒ%pĉh!Eqq\5u8e !n DGGӻwo(,,}pa[ao߾OU>ׯu/))aʕѥK[O!E\҇bL}{Zn؈}6j%L[JZAڥ4HfO>L0]rqbcciٲ-+##шwqoooRSS|Njjj/9r$G,..qNNpKfkd>*9L椲ڜ]ǹs?]vQb*PǨ/UMf ~!g&!!-[Ç5jT%E2رcdddjժ>2`޼yƣqrrRm111fٯL2nwNۏv'q(M5>HPsGΕ+omFbv%,`ƍ*WnV[]ZZZ|Tk{gggڶmK۶m{h׮1ԩS@߾}quuu#N}["dN*9vD1~ncFft46f 2;yT|ůYwzƆ=؎c,I^Ϛj*9L椲̉RRB=y>tYY'|df&>?9WdSbL5jf˟{-]g رcnN:^O aʕ?L2^zpB Ď;o9W,XKff&K.%11aÆpa0Mܮn!ճ[×}Tև˘1<)5RBQPQ̙S~j{Ĉdff2|RRRԩvEW8سgO6nٳ3gmڴaӦMV˩Sݝ| Z!t'=Cɓhhe ( ?&HTlSw>XO潱յZ^zqjoRXtuL<ɓ'W{V:t(Cr{nZc !5(p 1$%uw'`rvYo_6n-e}4РPS [BBPx8 <1+ ]4hv͛]068yS8}z䜫Br%(8tD|qĭ߾*scth難$ !ڲш}Hl.KXbǤ?))ٙРPiS"VJQ2/'p47@#Kd߷&n e~_饿HB+&Y6>q"S_n0k (`㩍|~ }C ath]UPT@!2"M#7[h5cǨ]N_9͚5캰Bߵ\\TP*5kְ|r.\iѢjwBa>Ƭ,&?O#ht:/~j%!b@ĭų AV'.޽;+ gϒdJ/_ˋUp^D=Q_7*ER~58f .\k׮7aBoLصiCU+]'\=Ö-1ş1cv5Pf Zرc믿&((\B'y11 Rv%`R]Pٵ(*NAzfӝ6m?2aBu\^Ei_88]PQai!>_w1"Z@@^yvS@QgǏOii)ӧOѣGϻȑ#uX!z.+Vգm$Yt6g7Ʊ٣Ud'Ndĉddd`22))%{6>䓤œO>yP!*RKxw<4NoqqDEͅo* ,[OD}Qd2UB\RZJ7sܟ})Sd} aRLQqQǻywC>Q/0**#F`oo_a7jC !0W{h3G]C;`mZslpB߰q `xmJLL\B`HJ"~$J~ WW~Sj%n 635qk*5oԜAcf Rk׮hd͑ IDAT44 <h4r}>BXӧI8Ҵ4l X.KTh2CbYˇǻ{wGfHH]{1뇋Khٲ%C !E?3?)/mhj:__Q`(`;B_V v>aj=Ν;-[2bjBarv&ytCS\jZrB7(7--Ҳ0͛77ס޹(4ŋ DE}1B>H6O48f gϞ;?SkBBk( K#Yh76Ⱦ}DFq$H]>w@hlTP1[ ֖/___YTauٳQvkLϗ3PedIKm( =AA*W(-;vÇӱcGsB!-S~>S^"j?CR,S|z3?w#OX 222̵{!J32HxYbc8:=eY،X>#ww&uē:-.\ӧ[oqw ]]]uh!PMɥKO!>m&Xcjeu&#{Wote@nZ-Gy¸\"h gx :>Z}VjeU l;qk+o}@@ NRR!egsZ!ꝼH2@XeYT֟\3FtȎ#r.f }+p2 'Q| 64}CB.A1ICT\GӎAhp('}B'NTx( )))t\B(> )MIAA+q T#ߐ϶X{r-IyI aNf wy'EQ*s=|':BԺ#GHxn2lZ$U5kvY BJ^ Nc-hlߘa1(L<,I) űc=|{$}Bԥ:/Ҷm=ҥj uo-[ ޞ mV33;pvvϏPkTPF"#I}u0p>@_ &n o{{r,6 l3Ol棾ѫY/ BԡZeee1f<==d2kѺukԨpӦMK̚5Grӿ1^ >CPPP#G3gGa֭9sBXD.#s <&?oE,^r^2?鳹$7fRIDf} дe aj7ګʾ}7nw_f_K,駟f„ DFF7߰l2"""*mI>} <<~H6l؀111swOkT2)B\~ML89?ڥY'Kߖrk>HπOzW_}ŧ~J޽|3gV۷/?S9x /r~ݰl4 7?ʾN6 z-um_K%QIe5cVi-]`f?u>)57q/kODˀc׳+^E|v*z= u888 F#ƽIMM9}QQ3gdѸVMDDͫ4zz|T&sR٭̉ә3|(66dÕ_N@}Rq0Ke@v^||9r!i> .AuM&:VٹVh*Ge2'ʜȌ${zt-[v킃:cIr^2ld۹mn ѣ֎US5ǵoY@EQ (;V [n>=< .0ۗ?tfИsߖH2n6'Eqq$MNɹ4=i`p{j}r<8QQ|-&2l-gWTdMa-Fj=7c>֭111 <|<&&AUbbb*FGGӳgٳgٳg]RF2?~ ĥYRS)ǚ5O?^>>HϽ.BXZ~imS޽;!!!\x}YBCC/"xʔ)Ջ 2h v~ˁ(--eС9r/X~ưiӦٙu!aHJ"yL ?\z?6mreW^I[neu$痭xiߤ !jb1b̟?:uĮ]8ٳ'7nd̙36mڰi&z@bb";w[g{&0+EQٹ7Ĕ Wq2=,)/u'ױV e}M0FtQc1`L<ʟݻСC:th۷lEQjM> ;2G{tl̴P%JMdueIVK)&W>]MZd$ڦM]&zHEQ8~gWk}[?.}BX BfHI!yf8HeKqQ2J͊$$IhPY\ -u(X1qtc48:r/~ou`gcǀ68vMک\BmǘM7+tB4kթZֳ[)(-}&ɝثYQ!D BC$ϜIijj.=dzϠ`0]^ڸ!48}pwB$ !,w"z5( _.].MR-QqQK~J_*C$ !꽢gH>ӧh!D(P#Oʖw oBUJQ%*.GAOQ7$ !TS|,IgP|$nC;U. wy@ĭ>!j$ !b2qu:ϲ]7vifsou\. ߧӦq+BX B:eFJx8?Mt^^*Wf 9 =mUq; CS+BX# B:RŘi2jTSQ!*6= {5i>P/}BIB1/oI8طnreZ_T\GhP(®2IBUG|~2;;K5l9'חknܰIBRRBjU.-ZSnjVksY{r-m/swpgTQ 0L@!D+>i)m`gEn7i>HcNpn !& BZ( Wׯ'mbbnn̟kjv &".3|~ OBX BZaHK#el{}-tޖKvq6lfe} dlXZIHBܶhR_1+lyW^ɘhll.ƪp`d 0&MTP!jNƌy\~-n>0ŋoVjxqV⿫JVO HBH$Ϙ!!4'~B&PQ)?oMŋph^nT)e=߲Vccc\87A!McH>C|s6qK(#]͡Ct_N+W !čHJff-gN=zv:__+`4.VǮ\9-Z#,8@ 5B!P+gO.ht:|C> ðpBa $ a% O ytJ.]iX/ʕӬ] )UJhֆqxiR4Ba)$ )dXAee˻x{v!!j]q0 ~O?saa6մBX: B4`%$ON>sѺ\L ~Ʃ+зE_‚VB!h$ )B\xly:`7_ؑ #FYf'@ L+y鮻[6:??UJ+Hcu|q r ;3:p4#:^Ba-$ рɳfc4, VZM箞cuj ;Z$,8m`?!(D`*,$mb}-ZC`*(S˧r @߼FXp< v!$ abcI  hkTl꼖RS)1bX84hݢ7aat\5 !LJ1i$~=bo[{oR`(`۹m[CR^ZĸqyMB!O$1i8zF>s6n\udfz6DNIM0*p#;C:G!DH‚(Bm\^SAF{{|Fty߲c0hި91@lg!'P Qz*%7&>uEHV{ǻxva|x xzW !> oҥj uo-[ ޞ mV[n_~xxxh8v9?o?ϩScJ65&#k a؛ xQ}l-xDŸBX8i&^z%.]ʽˊ+߿?qqq4o޼dĈ } <<~H6l(['MpźyBܢXϠy+0.l<6U#;dTQ;B!̯>̙3+ۗ~bEy~'b4#48Ams2Bԝz3220x{{W&55礦U\\Lqqq㜜e/ CwmRX|,#x-[寯xqNF7q/ M fY[VȜT&sR5·5^( |#""yU٤hv{4j2'EE|yK֞ZK|AYx8noŨ!OjJ2q>}g}[n0x TsBBBMϞ=o{{{+t:}h̹oK#I Ҵ4p8KTgN]-J5bD8O'zMC{dN*y#>L:^O aʕSLW^,\Ac[8P+WOrr2O+TTDڒ%\Z]V-Zj8 9 DEvE9T!RGAff&'%%N:k.Zh@||<66iݳgO6nٳ3gmڴaӦMkܹǗ?9r$s_&RѩS$OFs4=i`Xk8~ձ.;L 4>-`kc}!fb1 L<ɓ'W{V:t(C ꄸ9hդE ZM}O9v|> n~B!/ BX2CR3)pc[ r+6N> VF&nB! BB`$ IDAT(|%cCϫ rgrJr[wvCfQ&.:uƘcvޅB4\0cv6摳kto"쪸HKbMJai!N i7;ۮ]!D&P3۷R Z-Oc$45fƲD_.Ƈ{cB(MsrXk֠ `cC!C~ &/fuj\=Vo4``s! B\b0pxYY8׌8th_ĭremyS~!z$ w/iSr޺vm=c:_g/_fu|q y;3&p ; lA! BIѩS\^eyڦM|VxfIJ:5 ;Z",8[?l !!PF{e+( ap4 mF7wcɵO?^>ͻaaj ͭ]!,Bo㣪>gf2d&(E "E].WDdW\Q}nP,HP RBURBYH;ɤO}?frdHI'ɽg=$Oι\ee֯ BłPFG_r3~KBD䖺nKfMC|h-?c1{p\(k9wމŋ?m>Moas&u&"TzKcN)9pBQQ=@<.R/bS&9$b삇6á)oycٱ9@qcI㰆a  (J j0| 3 U<1b`˙-H<%W?~<0lq'/cU9l@Iwb5{΂֮y'@BΓ&ŬY5\9l؄o2G X:>&"Bqc 5$t9>/wm ®}l3ִqȚ-ِweރ瞃Cj"~8cm:tqぶ @^mc#QB!ت]@_[$saU;0 %Vmm,6g ý~@޽ŋU[Td_OmDcZ4 üVgDý N:;Omcm >NY`A[aٺսJã?C*KDHMA7 cGC&0~^"<3vB-v iZXAjD3)gr;+AU3,YuB&QN Z%BU0jbI*>RV k\%%>G1c6QQ^eKW|j32-=~%{15n*"x1vs{y жvU, 5„ N Z/]BTb`T׽uFN*JIZF@$ ˗[z5yyu_nݼ^.S( -Ʒ)m?crX$p5̀-5Ԇ@A$y:;%VE66(*H"=*e0hK.: .PCA\[Z6&矑*X32h &ID8}26!r*`J)~ m`׮-2ڰ6 %0[@ 80מ3 \v @uo'J]n#99#GvBoX k2yyȚ=%NЧF Uw(s!|26ڄ3giq0H%,cP^Pm'+A_!_ʝ앚~&aWM$̪NApdS@(sA(:*yլaWN^în8d#?ymQr2B&OF謿@.FD`jQP  roF &y\(TE .-LvmR8Jjʥ*#>k\GzzzlqG SbX0(d>_z5Z[5;J ׆]m=v0$$4JA֩!ްkOTî5[z<6²u_aP<첯<@.cXa7 ZtUk .'PZ&9Ceo' ,к8!PjN$t09(tCuk{2.D@Ae3az52pn"B0߀={6p|h+( ( 7!V 3Z7N챋$w:xy]J2{ 0O\Er?7yaX Pm ]%H}T $hn1`l mi#WeDsvTbBg*'nfL {DOR.u/QA?A}{E}

v)ϢPHޏ66p-o@nL900C(.Pb``vVU h[[JAQFNcP3Wl6F cʳda&{\ЕUZ˥?7q-jJ^(΁(f!& e3 9!K 0@-=t Q+10} Azu$/:c'D;[let-LJa.Tmv%W ̤Ctp7pr+)\Q8`P`T{&G.$:c~&rŽb߇H-;2 juȕaƍ\Vo͉@l6y: ̅%0X!ؑ_`lW t&EH}wE׮]oGsꄜN&l/Bl×?٩r0?!VXDpbΆ RsQ VK0;s(TC ^y^…i9  *BV5Ba !AbBr:#G_1 j2 য়~ya͚5߿?ɓ'ѺukäI+`/0qDݻ}Z o&6l؀;W_СCqitD_D{m9 ?@`oi*Hq9ӻ%ez8IZFlq'sL2`!ߡ٩AϢ:ϫvJa{]%QW^f . rպ%t:Ir(0\0kz`4!8  2cMEHm6ӱxbÆ CZZZٷoϟulXz5 336lx^RaHKK6ZZׅ׸j5|Չ°N( E6 D!9 6@*{3qL|qL|qL|qLc<5i dDxxpdgWeggZcue.^X5WX_~;TmN]-՞ RKK,B'C'w!Ph U PhT Á||̳nZnB1111O(--m*4&V:HD=֥|}cׅhժ ^_6ԅԕS ᮾ 5"$Ch84o-v`С< ăccccQ1ϚD LۗӃW!""=uJJB4c?HNN!#.nv///76MbQR={tO_~վ'!!;m۶EDDW͆={xMcI 0}t xq%̜90c lR?cדH$Xt).]zjc15gc1 c1Ƙ1cpc1g8d132cNc1 '1c~@c1?ӤvilPXXxӯmQZZB(~ccccQ{?Zj5a1X}!(( $ r\z*t:$Mvaa!Zj˗/Ck7E____o"BQQ pxR)o=z|Cě_{*gc18d13K.mJd2 4r9pL|qL|qL|qLq<Oa13<c1g8d132cNc1 'К5kжm[gϞJb }t øqpo!WIDATi2VgFhh(Z-|A*s%3Z3gl6W={gϞ@ll,{=4ƘXcL\~FwuDK"** j ¯u|L>AAA QPPUĉ8p j5Zle˖.]@RK./n]kp8dmjXl\.X~1cD/|cj]r3ݎE;VETTf̘Wz]ń bJbb") Zn|RRR(**f͚%^hhܹtIZn) #i1=piӆuFs[Lf3У>JLڹs'={V,rJtD'NI&Qdd$eFA񔖖FiiiOG[, ɓ'Ӊ'())t:ˤL&˗SFF-_r9Ϸ'*Fo(33>s իWe{L^$@_|VǤ~OԩSo>ӧ-&pݛfΜusδx͑KhϞ=DP((11Q,sJm6"rJt'|B*, -\:wuz+~bZTTD:t8pcL-ZD 墈Zrx#"'O_@#t)""Zf QyyXfŊE.&NH#Fi7z5j=c^&L@?0_L&;u˭P]R\ՁG]s ?nDl61l0Æ CZZZX,HOOnjkTTŶ۷񈊊 >VU"ܷoOCn7ʘ/Qp{ǘ|Wիz!{Xnx>33^uUT8pWLЧOL߾}UfPTbÇիpX 0vo8v݋#GϘT֘_4D`Fd2t"<˻nL\s=ŋcɸ;0}t̟?_5ǘT֘_Nv'NDff&RRR?cj `#T*ѳgOxOIIA~Va֬Yزe {m|Ϟ=P(ښ_~ElkBB~deeevJ={ekǎի E!Cp =zT| ӦM?gy~ 111m"""«6 {b2b*?x-cDEEM6bvcRZZ GL&ǘT֘_.ߙ3gsNFvc 9>C:y$͛7Z-]pV/O?4Qjj*eeeR̙3)::vI%O B;wRtttK̟?N DG6tnG}ƍkj0!yCW1Ҷm۰a"66 ]zpڶm#G஻/xXu8d5yNRi5Ν;FQnB a5'<̘4h̙ `0 ""K._pg8TH$l߾ݻwZ}݇\|w^ǔ)SPZZuÁYf!88FK,ꩲlXp!Zl V>} 6 88| tJ/V={wPTŋp8HgϞK.A"M65,)) ]vJB6moxoӦ ^yL:;Ub'DXXz=>;vL@CRR`8x vTr8عs'e˖j۱pB$%%aƍ8|0ڷoÇ{^x8tr9{1ܴi"==/^JX!Ƙ8p 0wM-""L@Gڽ{7޽Ν;2+V t9SO=EÇw\\\.آE(..Ξ=K\U!C?ODDׯ'tZ??ԩS'{HNz-:SNCz{K.1114b2&MxvEz˽ʴk׎K/D rsskOnn.'NQ^DD<;IPЦM6hժUDT~Kt:mذ1?dOuHuáhuu D"~3gt "tkϞ=^ÞJҧ Uedd !!^Gqq1ֹ߿ױu܎݋X\\ ծLvĠE^9wNXzqҥKunùs`۽ڡP(лwo*522 'ߏ+Wz՝1t$Ta;D")VLJ^G"zݺp\dHOOL&:(~VO6]uTr!22Y Z1cЪU+[QQQp\fW*קǪVpO:~-;KHLL\X@Ƙ,\Cd޽;N'rssѾ}{WDDDӥKy%kiiithٲew^ciiiرWZ]:w ѣ!}U3 ##K,!C|2Jz#j߾=JW;v;:D[ǎ1|ر&L~X1CVo߾Xr%N<~K,i׿|2,XӧOO>;#sp'ӦMÌ3edffxב\<3|2fϞSNa֭x饗`z-b׮]xWoaƍ?^~'Z }]|b~$$$`ܸqؾ};.\4,YY!!!0xqY|X`W0jl۶ 999X,>jxsa۶m8y$??xPVVYf!55/^O?;d5<fU룏>c=^zSNXj vS=c wސd={6|Iꫯgŕ+W`4#G>-[Drr2{9y0 x}W^Add$-[G}ԫܳ>ttx70|pd xǐ{R3gѩS'R/ ocٲe{ڡ+Wra(**B^}v)2 `ƌAhh(&L_~Ng5,1jԦM̛7Qmsc5!`c1? c1Ƙ!`c1?=1c~@c1? c1Ƙ1cpc1g8d132cNc1 '1c~ ӝIENDB`cykhash-2.0.0/doc/imgs/set_vs_pyobjectset_only_insert.png000066400000000000000000001405261414255425400237360ustar00rootroot00000000000000PNG  IHDR5sBIT|d pHYsaa?i9tEXtSoftwarematplotlib version 3.0.3, http://matplotlib.org/ IDATxyXUaE1qLM JSC-IXii7ҼíL!<`T,J" 8?N*(GP<^k9/kF)B!YYw@!B>"(IeZj9r={LBCC<*/0g*  BX[[ϗu7@!p!!!h4N8A߾}qrrRJ <7n >233ԩ5ҿfԩx{{ceeE53f o.6mI&PN.\hn/^k;4hЀ/Ҩ۷o;PNqwwG?x EѠh *nYh>>>888Hy $$$L͚5ۛPX~ܸq+++Mfh4}3334i₯/=ϫ?~_~lllh֬k׮_|9*;vԟ. !JB<ѦO&Ovޭ>seoo5kZRJh?ǟ9sF/TJ)锿PӦMS{Qsח?SըQCծ][-]T_]_\\\Բe ڭTzgշ~٣yeffBBBRSSUF1cڽ{ڰa0a:pVvR>|:r:r䈺tR+@7NٳG۷O}j2ׯ_WjRjj߾}j̙Z)W^QjR}]eeeRJ+;;;矫p}v5{lV8TvԚ5kԮ]TPP+11Q}'I%&&+BGO@!p [oeSZj~[TӦM ʍ=Z9994'1s1(f%Kyzz*F vU999 T YJII18vرF%''+1c޽{7n(@M>efرe ϝ;W̙3ԏ[*@ٳG-//OU^]OqƪO><f͚\zRժU'֭S /q?\x ^`aaAxx~ۄ СCrJ 0tꫯboo 7jmE5;;+`ggG^^GdggsQv3>>*''W8pw-޳ڼyԎ;RJ1BU^]?/ѪU+sϩ/B=fĉ Ǹ~\ݺuU={\|Ű'(ƍk׮9siӦC@@A9gggx -Z(R׮]#55mrIONf 4(_zu^~eBBBVVb޽|ݵ ,^]v=///Ҹt۶mE8q"k֬wL2{,>0kj֬6l\v6mjՊ^zѤI*W̹sXr%[gsŋ޽;QF 9w+֭q,YGGGlll(u*0HK/) "P={v{ァ<==V=zuA9OOOճgO~zըQ#eeeF G/_n}ذaFR6mڨ>Ƞܭ[Ԅ TڵrwwW={4)۷oj֬V2dHb ձcGUjUeeeWɓ' ݸqC?^y{{+KKKZhGJJ-r2eU+WV֪N:ꭷ/s7=.::Z(wwweii<<n03O,gӧh:$ !*ի/ܷBGGG Tug,պGccccC1ԪUK=^I /:99$ɩ1111<}b^B!$B!`$B!`d)#??DbaaAvvv}IV)W\/$E{RbbggGjհ*!PN#..sssWUt:888TE*$0&11D)VƍQn<$&jtԪU ;;jHL= 1Ғ].BQO%$(;!05B!`$F^|E&NX(ӧCB!ʒF6nXziڴ)/~< *}67o.:CBBؼy3QQQVBLJ$ˆK[êTRYwA! :ȜX #w^O>aذa8::Rvm,Y/j;v,ժU///f͚ߟ¨QpwwɉN:BӦMYt)uښ!CpA,XFA>GDDhرc>>>ЪU+N:@FFNNN_m۶aooOZZNSNRJFEzzQ{s FR9sPNlmm1Ǚ3gٳ'NNN8::Ү];bcc aŊlٲEZ-ƍ~ٕ(˗/'44h}˗/g|#~nƖuOH2()\Z+(5~,!Ϙ9s&>ׯgѴoߞpBnڵk]6W\ʕ+@AӳgO\\\ RJ,^Ν;itk׮eÆ ŋiܸ13fJ*dddܷ'Ofxxx/=dٲe__𵣣#t֍_~DFرc cccCxx8 :WWW>"qF-ZDݺu^fsi5{o {Ǽy󈈈~\|u /hΩSHLLsyf֯_ϨQQĕ+W? ;;;<<<݊ct+VPfM6mD@@#FM6\vիٻw/}YYY|/ॗ^O?jժ-];;;5jČ3oĈ|L2e˖1tP~]WU0N;vF Glmmw"wh޼9lܸÇ?P܅ܸ6CN عBPCY& ࣤRNPNNN 0пuFrr2͛7'!! JT%ѣG]6n"&&)=tfffd'nG5kaÆX[[se:t(ìI&Xw@-NNNۗ ^}A+Js ^l J5}xh;޼yXz5ϟ'&&uၳ3]tuӇݻwÇ8~=رcǓT93f`>} \]] ȭ\2}e$m:666 2ӧOθq 9Çٳܹӧ3v"Gj4io+V 66'N_b ƎKjj*\x+W/;{yyqI.\@RR|8ZaÆlc$''ӲeKOΝ/ uܙuҾ}{x饗 kfΜɇ~ȬYhРl۶MF*U8ptЁ-ZG?8rHի/nnn:tѩS'ZjU׏nݺѱcG_!(D[x4 +JF# *T*UDJJ NNN׭HMMI Aǎu}}wL0k׮%aРAji-'%&q\ѣcziz/.OQi{}W'%ə3g5kqY9BFJn!xi3a~/ȓ?Q@@X=z45k`i9sдiSVԩSKOחFOBQ!/, `ti www7!$$sVӦM,^ 6%^Ic~nBTK& B!JWM0._(Xc˷)7C)_}-Z?޳ 6lؐM6߸q#h2؟̸qWvvvԮ]ǓR&BTW#aIY⥜+ 5k8q"?9qڵ{w]ȑ# 0@ $ cǎdddжm[fϞ]d׮]ڵk̝;SN|rvMrB!p ԁI@YL\\>|8#F`޽E1k,ϧk׮;9N?~A@lqlذA?faaQ.B#BLN]PK.rGZ-lEsw-_\ BJ'B/!c3$ VjU<&!!D̜͛9໖1xfjjj*PznnA\Rtb?NK(u ͛H۽Gy%11Dӡ"77ss󇪫QCsq7[FIEٹW{+n*ݗF1x20%55={ҰaCO~rf"44h={3faah@ZZ-[>- zg5`x曤wߙ~S*HyDՒ?@^)}ݻTyHL (JGVqdMa&a5[Aꊹ]bb(_!4u놃6msNoJZ+YW\ၞ婔"-- GGNlTY>177KKK,,,(lqz&dYGʫ'%&Ҿ}Ry޽{ڵwGʁ{N=`[ Ԕ)S,wn[njFeN8a/%%E=C*##NIIQJII1ڗΞ=J\RJ[n:atAM0A)СCUZxlNN3fPS}'oV#GTnnnQuQEEEO>]oFy{{+Fx 3^SF=jҥ PE*..N߇^5|WOJLNZVm޼YiRٓAbbDR7RjRy(4{}We>o/[fɒ%\|Y<7x5j/N0駟һwol¾}駟u&''se] C#33UVE)EVn~t:Xh5g1sL}֯_ѣi߾=g…lݺkRvm\•+Wٳ'...QR%/^LΝK.vZ6l؀9\xƍ3c TBFF]8m4Ξ=Ν;quuҥKdeeC;v]vXXXGѭ[7N<ɤI8w,[ @7!, vڂ%^ʺgpܼy3fput IDAT7nLXX\| jӦ W>`ڴiҥ ׯGQZ"xgggݻwgǎ۷Ν;3fΝNEEB<.M ;\RLs$F)XNG9vV `ߟnݺLIHH¢ėԭ/|Bnnnv1ydΝKYf wAB'."f*x]%*0yGbo2a r0#F hYVD;޼yXz5ϟ'&&uၳ3]tuӇݻwÇ8~=رcǓt9~![lҥK9s۷ӠA^u\]]ݻ7?#qqq~Y43{}W2P!r0>W <%^ru}?@jN|Z39]OdQ=5k䄃OB!J 9(4w`ߕL'CZ{6=b2( `ti !ce#xxM|ŮywIRp_Эq5)& x,cccB2U/y|COQ&m6;7OhZ˙jF-;+N@!"ȸ G@쁂-G'4n3'8{=uWKsc,I(B}(4 o`8;;q&uaW#N+nvo4Ke(nvvv% %::Z__FâE޽;x{{n:x4 k׮_ƆUVpaڷo-jbddd]j8::kFbb}tEQn]iР+W4mFEժUql߾7o2h j֬>,ؠ'Ϟ=K=pppjժ?6##7xUg}V⾮F\=PK@Ϲ&Mngjm$3%7_ߨ*aIRJY쟬^ĉ曌=pBnڵkpVK={$!!0"##i޼9;w&99Y_KXv-6l ** ҺukF׹~:j*V_'Oܹs_pww_&77{{{Ȳe /[Hff&ݺur/[}1vXcϹsgӦM޵O|˖-cѢE9szsA^JDFF2l04itM6mڰpBmҥK9w\>`y5jsŊiׯ ]YZZĻA)խ[T~~~}WZz~͛7ZfRJcǎ)sssuUR7nP*""B)Ԓ%KTʕUzz;v(333RjȐ!EedE)}߆ zR*==]بÇwjРAJ)NV-PaܓbRܸVZѣG+S?A@5j(m?2333qY*--M)֭[J)-[*U/ߦM5rHɫz衔RjL]pۣG;_=RӦMS~~~ۮ\u|_;Zڼy]_mRF+5ݩʺ`U3:ŁÜfYwE!s}5iDoF\PP]v^zt֍^z@dd$TRŠ,bcc===qss+nZoիzhԨ~-SLaʕԮ]p9|||Ѷm[t:.\jժ`gggfzz:W\Ӡ?gϞ%;;]lj4k (ڵkWrqoٲ%ݻw祗^*qĝ-|I|}} ^GFFr%;6:84h' !**dt:/_aÆ׹s5jm۲` 5kg)|fϞ͚5kz*999Ixx8FbccB⁕/7rx{m?^,Чiu>zY%x\okNJUVӑ#f{bh_͛7'..;wo>ҥ ׯGQZ5"""s }>,;F_|)SXlCWJ[iPavAwY[[`k[O͉eƍ>|^Z~}t:?~ѱk&##???Xjnnn\|%ǝ>c޼y̟?g}{{{&Nxu:/~Ѿjժqb_b GBvJ/:/C&i9X1wc^mQXh4Yݿ _,yyY=thjNNN 0пuFrr2͛7'!! Ⲳ"??}9z(k֭[P~}pBΜ9Ð!C6lȊ+'1`(::,}RqQYQ6l5/_CEI&Xw-NNNۗ ^}ao.ɼ͛s~":u$fϞyAO }ؽ{7>|>_^^^;vx#i3c ӧ ʕ+ӷo_&OAcccÐ!C8}47@_(|;|pΞ=Ν;>}:cǎ-2QwttdҤI[XXN8_~Ɋ+;v, 8ǏsEV\Ʌ 8y$.\ ))\}cbb(^^^ERR999ŊuXt)111L>&{8rcƌ!**/uVƍZYY~[2sbɓY|9_5̛772i$:t@ׯ{ՏZڵ (HÇ9w$$$Q{r̘1$''3h ~g~7ðaÇ3ydeyN3]#`hI)Yc@A7Zl$O2X+M %qL7̛7`>}RƉM*{{{:w~W}T5n8Uzueiijժ^uueoFpz畭TlllnٶmjԨR-[TQQQFe߯vZ}'OT;vT666E9R#Rݐ*U(5b.~ӆNS ,PSMDGG+???eggUvTllRJDյkWqVS P˖-+2wԗ_~v o9qѱ?U&MOyyy)kkkպukuVwRJ}WN:R=3o5M5tPUJecc7no߮׻wo|x ߓqqqJ)bbb+_8qƨ45x`eggV̙S %w@L뱊ǕQ3*ǔ\j[y]5pu,krRJLjj**U"%%'''}퍍Mt$?tؑ[nww}DŽ vVVN֠A077ׯg*=hشiS~ݓa??KXX=zEc r9|KƥTa&kSOdQ=5k䄃?b1g6mJժU:ujҨQbZӨQGr|ӵ ӘA0`IIsN^M%[0F=dP<BCC Ҟ}IHH!!!&cӦM4Y/ӪU"^""B#^%;7ݱˀ浝kͩpK370l>i 4(Q|ǂ;666ܮGGO2QK\JLg~|Bsлбp/v6&kYgV9^gh6IB C쁂-G'`aDlCLrLm>Vט4/?#$ptΔ禔mP\6B|nTWo@5Ǵ-U/֝ib˶cO0IMpTff=KQq%-K&|B箧2ۍ 4034frssMnqz#\ϸ@xm*,$&bnn3ٕ蹉:VKvvyCQ=&J)233ILLss 󉓛;&Aj%^R|w23E5 6U*&i$RrRnC Bڄ|˸g$&OKB)ެSRXh,0ce^#$B!)<%^3LŹӎz 4Ys?%3$fceaƴ^ ܪv΅).̊MG( 3I?ʚBx) ļ}1qךӰz.{L?|8#F`޽E1k,ϧk׮L:SrAϟ@`` E7|ʕ+ҥ VVZ۷>M!Od0b6/{ɤuѤdhm~M٤I*,<%+ϭDtظ~1Q#ZH q"9rQy/Jdd$T^ƍ!_aq^Y =?3I򗓗O38)Y4YەiK/ڏgWS:zŖ[1R#IIISjUUV%!!cJ^ IDATTnuXYYQrbדCNNujj*P>~ +zWccccD|4ZTeo-=9kNrZw6LZ+ R}7,8 6Pծ*lO^B/OJ'WRI|qݫYfj}Ϟ=ٙ{5I+111Lɕx&ܠxR3~9߁Ko&iX9,?eߞRoнq![2 笞ҏT¢L'S,.2O]]]1777uKLL4+QwCr-QDڴiS1SNֿNMMVZTnw^v*4EQ11V<I>Ҙ>mgu| ޟ,2al?`,g8ޞ(t'q&"ȏgkqvj9"e8 ]>Q"`PPo&<S4 }!33baQ !x*c{:l8G7+Ƹ9#GQ 70夡Qix[-,-YiS" >|Cs\ݛ޽{?7|7|3>mllXp! .,PBV׏nиotu]F&6) n_J%E{;@Tl@xpꔯScţ@!:6~(_~_w( _O7PɆL fq gW0m!9i9X[X3oF-4wBmLc]%lL-=J/wcnƔ/ge'n,QD'Fĭ v.'P!Dٕr RC)j ?H\ #WFsN&*&vVՊ} Aˊ+Xrt 94vlOhPf);QHBQ&.lw!U^_CޏK,:B v,O*8g/ɧhيIM'+u5%@!eAOXDG x@{ٌ;KJGUc_ 'SgP B -Ne/Giٻn?gcPk9> <̻ddh3X;((TȔSx[;B(񢔯!<۰ٻG6T![쬊?0-jVSC\B^)뺐γs`zw_z61{yIIe_Yw~U"40Vu(tzTެhm9]wWrdQjڛ@ߺ}0ru P!Dsn %^&gh07Ʋ֔OJfYl oGoZPlcO B!Jv͆]s(/cY:l4Ո +GQM,RSPY0@m.6bx:IBQ:d$]ø]D%^zfn<͊Wh̢~xU3k?y~|ĮkSiPAA<$ !x݈5o8иٻMJg# ۭkAǺXig EQXw~ Ш5 k4!C(&P! 8P:} MLZ=,Ǐ빙Gv0&K#ZQ|b(=$ !x:K 59:r@?Nޠg噕,^H. Fa.&t(/OgǂڼbL;{T0]moW Eg;+0tY3QPpueJ)ڮe@!%O1x9|%WL,Lz[V+GQףݓqMhX} !P!Dɒr~|YKw *ڱ|=ǣ;Cs 'V,@!%nhxIj=++q_l~)mW1t2P1$vŷ P!DI`0l@Mwf/u!QuOWЧW?MLblN o_+ģHBd*2:2kgγpPjr#VEslYa0[3,:l}6[F5Eߺ}P]BJBQ %^6uIhayY) BZTjAh`(UiB@!EJdiL?3 S||u+XΠҁό{Ų!P!D)/ochNǧNXK =Vʅ;xy[)9IBQ4n+5CFOc-d|w; 6n̋^~"P!~Jg<ɺ#Q~;ژ;piQӈK3>fR >!mEABf:[|!t|*#Vt*վ#B]4wßps#40UZIB B!%^PA8xQSd ;Z?-jT4K@:0+"S P!Dċm5lgS?oh[וO^mLEYON.*UkFJ͊?!@!+/Ǯah3ШULT@]|Ea˕-88d*5#ؚ?! B!L/lxQ2{z*mYE3V->>1Pө&iڨHI(rx>$Щ{7l}MQ"/F2rШ4 i8$(`ˡɼ*YXYڵ>.:{7 |8n-é[$)2xbW ٳ'׭[aÆ( ӦMrҶm[N<>Ν[nHVرcM!JmD_F_NN?Aa]vYp)džZ2 ßA1jGvgߍ}X[X3&` IeBk֬aLa$l--ۻ4ڼaLg黱/WW1dwZN5jfKÇ8qwؑ~ƌg/KBB;vukkkڴiCTTÆ bŊԯ_&M`mmҥKqww' fgg};5ոVEw{ni%#7&5ɭPD13P0TGk98U3\cg;.Xٳ tnΥcځiI9@mMmvKUuzӤ,ܔs}'?𹻻#&$$~>W\@RuVu놃jwww6oތC9s&aaa>߲e vvv8ۂۺuk둛\VkbG/pO=@lq{þB'%;o4}- vs\[cgNvgƀ[-m;oOLT 1Ę[YxCxxۿ_U%ϗ~^(qsscϞ=W_ѵkW:DJrرcoEǎqtt߉Ve֭tKK=xMInrMr3Cn8 Ҩ/U4mgX;ZY[qtiaxq @;vLl:'|CY ^Y %&&溃7<0"IHHx s۷믿r?-^[bŊ\:wSKK")ʶFr=rk\}MUE{4S9Gg`֦3|z:?˙eh3X;((T?>yPYe<'XYY֭[iٲC ̵-[_zu<<<''']vۿĬV1 ;)!x<ċߕ"~ܪ:k 4{;^?JWy? !@c2`6mJ`` ˖-#..wy7xOOOfΜ Qhݺ5gϦ[nDFFw^w̘1ڵkSvmf̘+R|yHHH|ҥK's!IHb\#'s67^wlN{1|Ti9i|';r*ϙ!J}6ƍ ..;u-[dL2SRfM֬YC3a233>|8)))4oޜ-[=o޼ɓ'Ӯ];Z- 4 22ƍB'~(dK j7KYZ=῞bAcMצYOg[]Ww~ qAu0r潻(DiXB5R8r8ÇgΝ;s}ֻwozgӦMcڴiܧiӦB*;g Tn}g/4!1+9Jd uXͣdf9MPa4hj>(ܹCDDNN [UB+#ֿ t <4'(=|ȓdj[iv-tc<8*5}2o86#DiVG}-_9Ҥ !(B7)7K:UOps0_(~NjMxp|]|ևeA`Ags]!%qK{=y.#WFs)) vûmka~t=ׂP_MCv;K )!DAI B!ڐot_mB( ?G&GgцӬzմ`A4$e6[B5&+Vr\ʄ Xl>>>Z*?B;q(L 0c lmϢE3g...3ƬBa=ΙQl+x|M1 XZҥ>_ ljw%nȜCse3{e 'tիԪU ޽{oӪU+ڶmk !(xQbWta{9Ϣ3(xUeQ&4r6Akв|~uB!Q%^B57(,~aPk9oBJfvtb4!BzHp`*V4KB3):;;hѢ\z@B! @Ñf,񒘚ŨG6T![YX6G*\l]b gb!DS|q|}}Qsɓ'[.B"r~|b/b욣Nʂ+J D݈",*7^;4'/5*0|3puz=z5 ?L!ĿZ{Kv^7}۾}y ?P\eB[ҲrB-(|@EQ:u*vvv?''A !xvυ3*Cj9w[TeJl, _wo[6NRf*T׏QMFag!+u֜={6 /- 2a08Ÿ0:ud:Fjk {7⥆ nRf3d㘫9V#U8nn[a|;w0B)y/Vfuzfn<÷Qh̢~xU(ܝ9EQү>4wPY0w4~kV!D !(_K8{C/t9)s=a)$>:n ǧO,0 BQRi`8¸]E,%^"^ge(og'}Ӯ{4(֞[Iצc0wj"0 BQQ=a9CWhV Qɩplǥ1m4%k#[SӹfB BQ_FcazpE/"KƖߧ_~X ?{XQ4$ !DIQD%^~:|ΐ5`MDj) D WjNh`(^^jWQL=_|߿ooo"""^:ݺu3+/u|Ax `zZN|Ųt[AY'*PB/YcK/qz=`\#8""¬BR/,kc ^(t~p8IZńNuY1Y_LR AbtmsU?!"&\p!_~%ݻwg֬Y?oڴ)|'^xQQ 9zV _ -jf.G nLj$ 2)5BzETnkMs}<ަW_F@ꝙl"l*jB'ǤXzu=oڴ ))yk+)*w2P1'6ś^6OʚkpucjTz-8OIp{dee( 'Vb̙|WBu-b0(,}y[΢7(xWca?UqF՚{ FBzjblӱ8Z9jB8h t:&L ##ɂ ۷(O?̃30g{ٌƕV޸}9_$e+5/8%e`z-z-0 rBjJ fMGILZ&=e-IJFHYjBЅ]\ WHT!Jcu{ ^&γpyjٳz8^Rf?S [صq)(L o&$$;v`xfBѐ&u >WG>n`Rܻw/{qcy< (/ZyeKTrdQj-b`5D Cڊ~` ,/DYaO{z4XV%^&g0rU4G``7/Ңm]{ШP$͟aTw^1 !>&ŋ3qDBBBrR'JQQM'♰8iY:m4݈N ܎^oYzb)ll5n2VdRtvvݻkEAR28!(2S`08q %^z>43 UXΥc齥8zJ ޴wI^ʊ+W$!Dߝ+f+r=[TbiQ;u9_'Fpt`B tM~g !L 111DGGSBQ?o@e/1yC 9z*>ynu}!\{K>)+j|BäشiS^*PQh`p[vsYJdl!9'rpu5y|BǤ8rHFiذaI 52D?71x}P/gR2 Pٮ6=3OBBv"_2@9rlhI0((LR$!De/ϫ$:n,O`͊j'-'Z uhZ((L BL.RX⥼Mei ^_Ц+iuuuIHO> U D ![x{{)/^Lձ! ={u6lEaڴiT\[[[ڶmɓ'soѼyslmmqqqgϞ&_Q ec@&a ǯݡg{x<X3 )Y)|CFlAbF"^^|7L *O//йsg,--_W^) ֬YѣYx1ZbҥtܙSNQjB'((>=za޽{i޼9sa|ԩSӧӡCΞ=` o3f̠]v(‰' 4v!D)e/;t^ٖiR5GE˿3ϙ$g%Vy Vck؄eO`IHH͍ݻ?r?S?>C aСDDD￳dfΜk:t@pp0ڵVZ(DDD0ywVX;+Wdذat:Fܹs2defe_`gKJz7x;sz51GObF"LgrExp64y\B+`0<VNNfĉ|ޱcGz3fDDDwر[[[ӦM6lGjIHHϏyѠAMvvTcVVknO^6둛\ }MR8H :e :mu%1?'!5K :f^T(D^dC0g0C mInrMTGY:G1iw}GPPիy7VRRzww>www'!!$$$~>WVtӦMcTVO>6mp9*Tߙ3g-[`gW%c֭EJGnrMr38_)s ܇/Ž!8P1N.6 p;MbF>H.=-*]v ''₅E}#=<<J*=t?ukkkjԨA\\CuҲ~h\k ? 6뿂ꕅhlM{$F9Jҽll,ՄwՀ*^ҝKFrQk3AOFɃ(+癗@T**ۣp^Oll,:u*`֭[n[n=&00[>-[hٲ%իWÃ[Oڵٳg5gϞg]|R6Bȕn^ +xq<3M7y>yE,Omw|5h6[[֠NcǸ]7j+!(=ѣޔժU8 Ǝˀhڴ),[8yx <==5j[ft֍H =3fPvmj׮͌38::;̝;W_} xJ S1(zP^*eL_͡)kEHZYӷO™3lEhP*Wz̑BaPUFPP66[Q}6ƍ߉C6[lի2e SNf͚Y~ @ &IIIylٲ~ @sh0`4oޜ۷S鋺 !J{md,B>u>X.l;}q?N{k 3z6ƕul>/}=NN|̇t5ߏ&8p@X5111WYo~Ç3|~mΝ>ݻ7{~d{*iӦ1mڴGciiɼy7o^A+x\ ނDByɏ|stl>W{Kcz:_GB];0$\l]LBI <8W'Bv͆s\3ɸ\uc0U5&v|3|+ODAօ' &G! ʤoh_T<BL7`Pϸ 4L;AZ'[KnD:vtٍόɺ`L Gԫg_BQm 3k42}WVOXyX&*X6/$c#IH`xۚPKǗgs+cTQ߻BQL gf„ ̘1 *hU1"_0.Fa#._k_c1dj[1>ۙ,6_6Xa4qobX\L /`|Y}|.@O̩_DGT'T>/\z1?b|_e͊Dw+EQ-7f9;wPY0@m.6B2)رBh`Ts* ٮ&~G.&x bQ` u|-,yOxKHOnꔯCxpTl`X(۴icq!D%]oB #ٛOg"eӬ>tGyygP ;jK5`XZȚBǤ{<޺uk#v'u4c)jmQ6n,pS)&_Nr+-ͫaz8஦^%t(ȥ©lH!(j&m쟵@!DɀM {w+8V%bjd [Ok9fhػ~zNEd鳰aH^,OI0%%mVKtt4SN?6"ӛp 6(2Ac3֡QxmM{6y ) xq{4'e(^^&B?S:tښ1cpBL!S@ Kao]11̬^ w +^1_Rtk:^{ɊHBIQ\]]9{9Bui8qf; _/WS:=Kv^YY0ź 'N¹s҆)-Q. !DIbR<~ۊϬYhܸY&3>M* h7ZWWp .$]=7>إܲtY,>'W`P 8[;,;]?!SˤJBQ>oѢ|Y&(}O}8V_Cn*-K˜g\}]Uzl;|0Q\I@jهT{Bt&j5H{!D!e@8qKsPশԟcHHՀ*LRg;jMx"vѐV\c+233լ,0`"g5( o>RtXU\ FUޥ]3 v^ @UL JJ,%]ޤsT\> @HH5j`oê z4v8&3`_dAjND׊tߝnym'n% !J4iwfl޼1cưyfشi,(`H t  W7l%l;O΀Za\Ǻ }&=y$aQaN> ?+PX|9/ÇVZԩS42a's&(pc|[f^u9@ix7ZU3kYgVbP 8X90.`=j@*x!x(޸qjԨ C- !JX6n7/k|7-6*,Շnlڴ)cmg0Rx\l "I`ʕ3mHv_&vMdʆ1N6W]}hoV}q siSiBT(X[[;3+_|#B<:9p5>u&n&SD-m]6Y}v5 MG0@5&U@*P8pۯY#(^Cv _RG3SdhQ`PPry ;|0bnȵ))QˋjB&]֎ng09@=fjDc/}zʣ0A1ٟ8==Ԩykh2;K;sB*̈́'_] ҫ9u I?A\r]V"l<\9qqTh@6 m2UB!$PQ84δٱ$x|KI㍧Y{l//y\.ǖ t;7y^5z }JBQIBju,hӡ+X ?0EQx~9T*›/&>ε{ؾj{&6G9< A ! B˾ñꭡCdʆ8{ ZnՐ yĜCsk\ΝI'ѮjBAc|tTjh ύE ϒB{ݶ53 L\wW]Y=R!.޹Hp$~AOG!2 BJ5w9uku_@9Y_캈Π`oxYUGvg2AƖ~#_?B’ߤB݈AJ,5>G:9^M\JJ;P)5@*m|+|BHB6yĴl5bb8=(D^䓿>NTM*wBFAqa`HVchqfj!1~&]1gCZԨgWwcGJ8@u kcB\$ ! Z>EjBG1Aaqtlamj0]ml,]%G1_/X߻ ZpB&Pnapqq۷7֏~{!sr ճ!+9աCreZybJ)TqbSBxyO+F/z={ݺu`mm6lx늢0m4*W-m۶ɓm+;;???T*G59 T _2? IDAT-X/Ggmyi]Nʂ>en]B0\NLEm=%HBbV"5k=z4'O&::{Ν;OPP رc 0>}p̙3h"::t ---W{&Lre)1!v̄ZMx~uo=G@ۺlӚVuE?+?† ?^*#TB'D3dJˋ%K^zŹXΊ}X3T)onRx{L;dj:SBypwaQǿ30, &.B"ieekof+KVVVfoVjZ/ &5&5d\cҥKkח"_v[-*@Y޽{ӿ9u_5..'ZOHHHLL/>&O󋨒 ל˳ )˭3`)-.^(2(:B?p:4KnJC%s~^$~0_?>&5&5dOζl -R궟 ̞=Lbbbx111DGG333)Wmڴw>-Hbb"[Fe1&g-6};a}p}r"BR<:BFN>Z ܸ"cKde2k,/|^pCSz`kRkRKX[W@ooot:پt3x6(<_dڵkټy3bpz`0XCs?~I=eM K 0x@BST+w ,G҉ pgڿjZ)XujSNu܁hJ;'m}bMjbMjbɞa/yގoqtt$,,sbb"5*5V ,bX~9f֬Yٳݻw{n//pBz{6 +F_0B[ L|8QH:q'?o:kk^cĆ\y';R)e*9KǤr5ۈFU6p5k z*omygP'?!(aEO nB)˳ygd3l8z~nuEe:ߔׇց>}[/]񗉅B<a{S F1$pT~/N1#(9 iUWԁ4CWPߧ>oDAҕ{ZB! ix(?c䒣DKZ<{{6 O:"R}xw7L91ogBPCoZ=HAX_+VC_b̒}\B\M|ܭ{Kڍ4ıZK2x-)B!J i(N'W  6_=q.˯t@sejxf6fWJK1P^!vF@!JSl NeW ͧ07 iZTb`t&oLT*F1Q<؃J!D # %EX*pvWh7 KŨE{ٕr z+7jtޝw}p9 !x(I(ăpp),Oh!xh'[O]Nqk^P'`R$vz6wB4BOƛ0}Z:}F^@>^skW`YjnTsh2sb@"zm@ !wK@!'`q_ۯn.6}ח`g3_Q0Ҷ1)y2OиlcE#-~g I(ߡ,7CSť k]>MEs5fɒKrbtDU*!⟒P}~Z3@&;[T-nW J)~g6WsB6wB!i'7~ur)_aз{s#3]ΎE?-%377T.] S!¾I(ĝσ_߂M *ÿ>eE_N"+7w'6O/rcy3aWkF<B<8 W.E} uOvn9ؕ'n{Bߵ.e~Ǯ]LJkhߐ S޽IA!#i(R X9\A x''.@OTfH*8qGFn;hОNx|MB!lF@!r2,G~)nj?/כ7n.uiTJ)VZɴӸ|2t$:,C!4Bى5Z=<92b~V@>B"v:4q[t~AA hB!Dq\X3 /9e:ki7qiy:Uç>eyMFZ=}kOhuE?W!iH?^ <݌IA+#%ug1uT]?>cL1rBI@aߔmB8ȿ .6gl=uNaL|&YguήՏQUVrBK@a_c +? ?diŨ~##H)ou}ݲ/+cy7܂\4ً~/z)!Pاckp" z7a|9:V ^/tnSL!%0_F%tOC!;Ř`Dž>!O9FyIpZ3M5miL6Ӊ<SAO^!ipFZM`Li#7&K92s]W}2c/tݪw?uC)R6JF!>_(Wx~>~ٗ @*\7'Kn-o{؈TfTB{U6aaao_h!!! BBBXdqL-8pSӧAAA8;;SR%&L@^^}OHV| V.lג/Rqjy:]̾ #Ї3~ɓɍ'E/B+ … :t(cǎe׮]4mڔz"㓓ҥ ={dϞ=ٓΝ;esә9s&>۶mϏ֭[UÇc29p.}cƌy 9rN'h7So`[?Nܵ{k׼Z|S>=_YV\ ]uah_=ZMB!q x̙ӇW^yxVZŜ9su뉏oE)E||JkʄvST o:ɴ1(xs5V~E矾&_s_~ 酣֙!%4d: WOFKAal^P?NfW5>^Fu2<,גQ FP* !%4d)ȇfi _o»t?>pWF5Er-oh!BXPK)A$k&Fw /H۹[9SZzMZ}qѻ:!$ xsat8 TBz C"5&NnG񮸜i```ގzUmBPPn-ǃ^d/9i\kp"]Zպz!4iŽ _?n{WC;ԕٜg9NNXmEW N\!{C)%$ :@hh:).~> r\ʟуhQxǩ s\ z5h ^7 IDAT7Z=/+^aÉ !I@)ryR ["h h܎AQTYO^!_'mwn5#]x%hz)^ޖ3B!PXR / J&0|07NDZ~'-B! h8k'Dڅx Fr-^!!$ (n0h޼9III׏d<<<04lV撛k,|hh4er=P&B ͘67/{ړ[[޷#5&5&5&5d\cҥKkח"_v[-*}||}߸8&Nh?!!"_w,9#7JC)-U4Nm ٻ<m=GjbMjbMjbMjbɞꑝm)؜[|R75I_57&&hvff&ʕM6;׻{4雦Ksz7z2$&&ҺukHMIMIMIM,c=n]g6otVgӭwx???,1.\ŋž``0Xa0V˗vRkRkRkRkRKT{vl~#aaaViԨQOHH0g1ddduVs̖-[(}B!6?MϞ= '22O>@^([,qqq 2f͚1m4ڷoҥKYz57n /:)SPJT”)Spqq{ԨQmҷo_>c^}Uy"oB!xTK.\|I&Jhh(˗/B F`ƍT .wȑ0`^JDD ckl[@Y !BFh եK2331m۶U*))I%%%P3Ϙgdd(___յkWo>h"ysLRRtjʔ)СCjʔ)Am޼&O?N<{UT)oyk|r5vXh"%KX/I\wM]|IpBua"""TXXZM?# ` ӠAտ}իWWGьt+ az`s̹sVU+WTJjܹsoV Rjȑzկ_?հaCvIiVVRJLLT͛777XQF&M{d2)???5uT7o*G):x,JNNV:|RJ?Pyxx7ocT@@2LJ):wڶmkQQQk׮<ѻЮ];/[رzRW?7;%);PTSg[nU'K3uA5tPN:eݕ^{Myxxu֩TlsLU``ZzڹsjٲeKjJܹS^Zɰag}V'% X)֭[z뭷Աc_\\\W_}e:uP/VSݺu+rɏڵkdjժeŵkה֭ڷoZxrwwXbӦMJөSCSd^zI-[ּ ŋ9r9QIVVڵkڵk̙3ծ]wd.&FQ=s*00P޽wnnn#[HX}B Qկ_߼t(ϼy1999jS9;;gyFXsiծ];<==-(PJu֩z)GGGUbE5gԚǚO*44T Uzu'X7Lj„ O լY3o>˗/=z(777z衮^jw^մiSe05/mqUz^ZhI6233Ր!CT啓 VcǎQɯZ^zI)U򿓹~)ˡ׮]CѰn:֭[FaժUԫWgggZlIzz:+VFӭ[7-???Rti7nřc@%I&HjjE3G;vstڕ}/,~mj׮Ν;aذa*h׮iii,_;vP~}Zjŕ+Wc?~E7nͶmXf Z:`2غu+W&55ŋȑ#YhgΝT\(;v,3f`888/у@mƎ;=z?:K)%;͛7WM4㏫QF):yԮ]ǯ^믿*_V^mS:qy_~TTT{רQCL&QF5j(:~h4ܹskժQJ)5o<ݻw61cƨjժY|J* RJB {uFBBB*TPm۶ҥzꩧRJYF7oZTTI}J)&LzJOO|ۧ*륔R/j߾RJׯ+^k<O*k/(@(rssS_|m'( vvq|}}qqq!88bߟmذ!Ƽɱc(((`Ν(Z*J2Y~eOGGGСCDFFZWƍ~:gϞ:Dƍ-5n{s^O>$]hYr%.\ ##jWWW^{5Fʕ+9x }%;;>}Qrrr8p ֭lڴm۶u)=,(/Lxx8ժUci枌ݫW/rrrhР:Aꫯϛ7ɓ'3|pΝ;e˖e1:uI>}_>}oo&L4޽{[ >;v0qDܘ1cQQQ@˗3vX^~e.^͚5׷j,XJj՘5kyf͚ŤIx7hڴiNdgϞdeeΪU(SAqezŅ cǎL8^/(94N?"X+VdС%1wBQ,BdkIDATagB!3r X!@!B;# B!P!H(BagB!3 !BiB!4B!vF@!B;4`hIENDB`cykhash-2.0.0/doc/imgs/set_vs_pyobjectset_only_insert_fixed_hash.png000066400000000000000000001402611414255425400261140ustar00rootroot00000000000000PNG  IHDR5sBIT|d pHYsaa?i9tEXtSoftwarematplotlib version 3.0.3, http://matplotlib.org/ IDATxyXUa lS#Z89W۵W&*h`CE<D8L2'ODmS=8a=]͛?#lll wɒ%Kk׎ν{r {݋8uT6oL>}xwyݛN:agg_~%]tjժ8::ȼ3kkkڶm+,XZjy󈈈M6L< I||44ݻ|n}666?ȇ|3|[e^~駌5ѣGX|9 ,0hBnݘ5kfÄqF⢷nΝckkrmB!eezcVVȑ#sQ~~~/hbbb5j'HV/j}!BLGYo3XEFѦMϧK.X[[s!z-={vܿSRRǎ vÐĐĐĐD1Øε0Od_+zݽ&##̙3`ۃ^$yZ.X`P̌b|9jer%"""$')B+SSSѻ7nsqq)VGٶm 6m_|ERRR~zǛ5kӧO-"-pHvv6ׯ_'##q+ʨoR'0$91ggR!?;;uft7FrϘihaaA˖-{KDD})p"""6mn]xxۢ+BĉXYYQr[ZZbiiiV_~Ԕ5kbaaQ?HZTlmm!|9QJ͛7ߨW^GASNrϘa,0e~ x퍏+W$!!AaÆQfM݌)Sо}{>#×_~t}޾}^ ŋg^ro0Cbb">>>X[[رc ,+++ VKڵzy0Z,*&0$91,sss~Wݹ!Di+ߟ[n1o<]FӦM /6mڰi&fϞ͜9sxؼywfĈ0w\tWZE͚5 fC^l2OVnݺ̛77|TϿBB5RJwUJJ $''I\\ZHN =+9)ǃ gϞrINc>XToHQf:vԩS;R`` j!<FT,;v( ر#kcɒ%z`` we׮]gPPv"&&BQqH( TZ܎]gI}B!B |;L* Çd4 >狊BѰw^7o[aoo϶m۳g*U޽{>}Ν;cmmMj;v, ֝Ӹqm%` .nݺX[[Ӽys8Ξ=K^ΎvKPPk֬/"**,&MDÆ )VW^Mpp0'OzW!'`E;KyGL'I).ǵڼY%s(Cq?ϟ{Ƕm?~<۷aÆ,]ݻweԩoo>zjժ+ҥ .]ҍ4^r-[}vLMMqss4mڔyPZ5̙3Yd ...{+\tJ*1x`BCC0`}tޝ_|7n0zh&NW :t+++"##gĈ8::ٳٱc˗/^z|7 :իӡCڷoOǎ믱'''3fpyRRR Fh.]ʞ={Xj5?(r9s Z !2vÎs]yG̑IN(RSQ?E޽gϞL0wyŋEÆ IHH^zKh4}"##9}47n=[qѢEڵm۶1vX oqݺuz붰F\Ǣkܹt 5kPV-vɠA=z4mڴիԨQ$+6l@FFk׮R|}g|GXXXj*lllhҤ c̙̟?`iZZ~)_5>>>ԭ[+VСC>sشiwu}X[[s}g\<<IM/j`)eoo? 0ݻsmHLL wwbiaaAnnnc9vuΝ;\t :~KrYָqc֬YCZZ(111ѻ${I222t۱cǰVZ4nKKKСC17k֌5k:\ӯ_?8p`BRw+6 ۱`aC^tW⩷xb6mą t[nŅʕ+ӵkW|||xW9p9rٳgO?=_www?N||oooZjE||Y`5Ϗ={&nTVT:t@˖- 3f 4ۛիckkLΝiݺu޿wNN^:7n,R~ԜXB] cshȻK@<*QQQtԉ;w<> 60e^jP!C`jjKgгypْ{jPpl_&e`^ XFIOOٳ,Xqƕz񗓓ùs8z(M4)վ♐[Y"B?~' K<5B!D .y%_hУP!%p 6& ;*QN) tؑSwO@w ?nQ8{.v*>صk111糮"&(3JA?ᛅyˍ^+R%I @ajժvr/>KCxj< O!D!3`8#oT2WQAoIx򔻻;?9r$vvvԩS+Wfee1qD\]]ݝ '''3vXsΜ|%Khh4?4K{yXYYѺukN> @ZZl۶Mo={PR%ݻӧܹ3TVcǒjp`97,ݶ^VJpB֭5͛77ٳ {{{h׮fK].bҤI4lb}sI]W~h~4 ˗/GX[[r Q IDAT֭[uh4lٲ;bee8r۷ښڵk3ydt_ooopqq^ƍA˗/^z899ѨQ#֭[ݻ;ggghڴ)_}nbȐ!ԪU y6nܨ700sٳ'8;;@RRnߴ4 -|':/!J7.&fs,_"'H)Ezvz2r2վ^OĉL0s.]ݻٲe /^dηW^$&&Ftt4^^^t҅۷or [laİtR|||3f ׮]ڵkԮ]HΜ9E?+Bvv6*Ub eّNݩR ?#[nL8QoCqy"##ٸq#;w$88ИfϞMhh(˗/ٳL6Craڷo_5ь9f̘A޽.mڴaҥٳUVqbߟz&M/R~̙C9y$CeȐ!?^;ɓ9<~~~>}???ǩSؼy3}^^?>'Od׮]Xvɔ)S>}:Gaر1HZ-=zȑ#_sbjj @ff&-[䫯̙3;?]/3y5:t耧'? 4H̙3dΝEtttMs.`UvA(.%[rrTrr u9[nҲҊu^:tPSLQJ)榆ۦjZ|RJI&Ν;+VkϡC[sϩ+V(;w277W7n(4UwQi&ݺ[n)kkkyfRǏW?PJ)uMennRJ\RUREػw211QJ)V˕.Ç>}(JMMUVVVȑ#z5J 2D)ԬY*/_~o߾my0z7ֵnZ?^)T\\THH^5vXu~211~A޽{J?wQJ)t۴iƌ98pٳRJ(u"oϞ=[o[gR)̙|}}o P/^TS~.׃ x\YYYj׮]~D_q)Bj*5^%-y,m,dPaggGǎHHH(R\ϟm۶zڶm-&&ZjQ~>Yf?DFF[Æ %66?BT8WB=p{ FǿwT1$'̚/R[V˽{äTXYh&V qqq۷2h vʶmje烏x* >?zh>3}]BCC1bnRJma}8w^j֬k~eǎ9r^Zz}jZƍɓ Siii^: [Y8}>*ǟ| /&$$J*1uG_/GlsuuE_Vn?ߞCb0(߸DHi4lmݐ?,9f9ؘ۔,kπ޽;oˋDtŎرcԩS;wp%݈ СCyYt)gϞem7f͚5銘'O+*;-j2qXZZ@ YfYޞ~wذaz-Zx>^^^={ӧOć~~*V\5c?C:r5rsҥG[_rePpμؾ};~oܼea!*)m$\[7W^V=ݕx-^M6q.]֭[qqqrt^}U8@||ŋ_T PEgդIeaaZjbbb :tHj˖-N::u꤬TժU՘1ctsBwUZ5ekkF7⯓6ZZdjР277WիWW~~~ú6'OTF٩vکXR7nPݺuS P%{ff߿\Thhhy}>sխ[7eiiƍu'8q`~Aw*UR͚5S|n?,--ڽ{^_R˖-SuU~jڵzoݺFUTӦMW_}֧Oekkٳհa~L)t۷\V 6TSNMw:tQj…N(yL)[}#>7J]~FR|eIIIdeffVVV[Ւ|DEEѩS'ܹ6l)Sz*ː!C055=<ψFaΝOk7%xPvv6aaaS$' qf;yj> aE9{, ,`ܸq^p9=J&MJo!(sJyއ=`gyUzܰ7(R .gggf͚U19sooo4iR䘞6l0kBÇ{̘1\vMb 111߿C@@@OZ!xOCgy˝އ+̲|O5jG $$|r,X`>$$nݺ1k,>LHH7ni= ...n;<رcُŋ4hW!x$!4ZBдyG%QYYYDGGȑ#sQMϏbÆ _gggzܹsu3=ޣ/^|E8rHޙ=E=;;[mvv6J)ZmcGn$uܙ͛x'z܇)|<$'hZRdggcjjZě.'b0:JUu)0))\;;;h=_bbb_Μ9ìY8y$899T,X@ppplll֙Bjj*YYYŊA{}Whh(fff<ݛ`1aٰaC_39"##oR3OrzGWQYX42Nހam!*3[Ky F[VJ+I3FM6^zx{{?UquYf1}trJJ k׷wo>ֻ۷s\\\7ndfΜɮ]ۛŋӼys vb >>>ÇYt)TZ|ܹs'Njj*jcĈL>pLLLx饗Xd vZH:vXy.e>}:x{{ʕ+IHH7`ذaԬYSwה)Sh߾=}}/|w>o߾MBBW^ŋ@衋 lذ={ȹsx뭷hѢm۶QFtޝ1c3vXz]f33sieÜ燍{Ʉ xwXx1QQQ4lؐիK/FM_dd$OƍEk.mۦ7n:WBonOHHEx{{PM6abbeP*WLTTX[[sBg !SV,lcBnxJyϭ[7o׮]iӦ K5mڴaӦM̞=9ssϱyfٺw]tx;w.AAAXXXp!,YBjj*kצW^̝;WoF݆ =z믿w^>#G0{l~駇lj'))/ʕ+={FyτtttO>|qaL;ީSx"IIIP!-+k6Ȥ%bkkG}7Z">>0c,h߾=#G~ gƌҸqcW^軣YXX0k,5kF155eӦM@+ԩC~hԨ#G$##C7"8f4h7իW/ !DiK=0+_-O*3BXJJ $'' 踸8<<<AZKOÐг~<(;;0z)>:'T S.+t>~ P!.m ; ZT_daTƏOZV'BLR ~`TDdPT7[[[K{:|/06C[BE`jjEّPTNNNXYYU{2@%Ѐ?M7 QR !Oqy=.y%hس)˘LB|oc5Hv5&{6{~Be$*}zz:`N!DѥaFMWO ];* Rrenܸ=XS6Z-YYYdff=oH> "91Ts"==7nPreLMKoQ(o-7z+]HX\\\tE`q(ںXJaHrbYIʕudg pvGr۩eC))ːF''b[6;;oe $zrbnn.#$F~&fh1F@ 'Դ_覦`eeUa&ɇ!ɉ!ɉP˛雜V=x+宅P!x.{_=VQ #"B$_ T-宅P!xrs,ae޲P,7.aB!Zf l W"E&BQ&Mq̬JhJyG%B!DY'8nsޛ=jzwTBH(B3;`xMPP!(]J7 yCm%B!JK}=Nm[~M&vtP!( i`p4kx,宅(B!DIݼw:wTBJ @!$b#ap PAyG%CI(B$şxfyyfNω'h׮=z !!GߟNr IDAT^իWYhOf߿QF ڵkÇΉ !ڼY_Nm64 _syG&D)K~)Fbp/_΂ ڇЭ[7f͚Y8|0!!!lܸ `С`fiW\R9W!LV:w- 'rLrS,տyח#GѣG ھ+&N#Z/@Ֆ8B!*{g^gj}W@R P#III?Lbbb$&&}Qܺu3n8ϧK.X[[s!z-={vܿSRR&;;+H~oE%0$91$91$91}z½(\"a3bLZ'2 X-+ ֕äЫW/7nܹs=Xyzz0o޼B  l><<NJQ"""ʤߊJaHrbHrbHr'_>,]91ӷtXyV3^!2-1555q(_>b{ѽ{wlmmٹs'm/ <ެY>}n9%%ڵk닽}(;;;l8`XET\p/s\J3Mr2 +V(ͲM{VjjFZ(᮸!0s~L40,纼9syss?*0'cbNĜP1@Ql#1 l l46%zwRMmllhӦ QQQ 0p{TT/v;ԩS m߾N:=й333 Fղi&lmmO\\T^׵Z-Zlu}iJ9s2&9 bpuYmܽg$z,a1aJ;E.1-̫x}ZR6mÆ # ;矓ȸq>|8u)G `ЁgKxf5TtTI?CxL8$ղŌWO@!a|S6KC6~-ŀچ#?{kSV$BQ9sp|y xSV(' FάcqbnGA]P!DŗlzP[z9t0MU;Wo+^gO %\ $BQ]?a雑!;h𨥣_eB 4 J}V狽6tmZB2$BQqa5=w+ Ǘѯ ˺<4[M FV$ :"^H(bޏ@ȷ`/5,IQv$`\6-кVkfo ߻x[IH%/+M@! 1LϚ*bŃ9~pbbpwgF ؋iy؋:0gF&IBTyh8ejx 2Ib23XbTبm?Z,og$5}c4V+m !n&&{$+;xjtTUh?b23dzt8ֹ~)Y| +c1*@ܫٖUU$B!ʿa3 =괶tTUցńq:4`*.O&sd +}qw*I(|;~|  ] R8e]caB]ʺLl9M߱C%Ezf>-83/Y(IBO{wMm^0+:Z6*( ǖ1SYJ͠&ZEQv'gPŎY|y'jg;k&uo̰h<P!D k@0QU9OƁx8x0utTJͨNߵ12<P!Dq4|/F-U.Z]ѱ׳gEt#* hYiA>us)a9 !(턵# ?{óCͦ(0:Zre]K~=Kf^4v#/u*tH(.b0Af*T j2[LusZ&a[N Vԭ;7ZfV4 !({W`U$\ -Y:JOQ_ƒ [֥V:v`L1X[ԽYe*ͤqu+$B!8X9& Y^m-UOXL*0eyz>u/0з'3R͡Lc%O@!efXsf3\-Uv3&K-u?QC+0|YҲu«}Ѫ^~G@!OQා`\@=arf*cߟ%qKԙ&lz2#`kW\cS\LQMB{7gZ2P!D*S!;S <4+\!@gMe]4!m˺˄?eS?7G-SF]fq#>!''  {A^aEKGUi]ͺʂ h*ufbˉ TlYYE`)+ { /tiG⠕2]!# g } >AR-c_r Ujy-'[%V80( nŴԪfkw ʚ$B!JޅhX䦃i{sKGU(¶Xxp!IIh+m_i e by>s5i~~Ŗ$B!JV`C-U.Awل/ܸ^y/y% Ba4¯oCv`mgٸ*ʺHYkEQd2a[Oq69 z5ի)}[xRA@!.6'7]fBWA-3HKJk'e]0ز..ȓ$bo=l{ol索P!Ŀs+ V 1𡱁~!RńqY|\|mJ[T.fx~:bR3{v0P!?tV@azϻ4d]aEʺLj9>ʺeX"zJ[eZmxQ$B!-gioR-嫣_².}3DE |[<ǭ$/~eQɲiߠ'tgZI'5o.lϚ5իөS'.^'Xf SL^#..G}޽{Xl°a8|0Æ c}ܹ3aaaw<)SذaW&::,g }%;;hV^ͺu>}G!tvǂAèHy}}KoC?v$@3>>/(۷OS-[<ʀڵk76___%44VzUd[pp2d ~MZYzu+W(jZٺu(jʕ+}VZhZ%##[FFwQt%~HÜ9shLt(Ey7`( XY~NFy>R鱶__ᯌ:F9vڬs)J{W~R_IinꏋtǾ*~oJwEaVҥK4nA /йsgv@ɥN#66"ۃطo_߿SLDD}766^OPPPڵkϾ}fSO &??Xufv| ۙzӾ} O%PVxO܍8<*]KT!hڸ #-;9p>;jAj2}v2+/ M3{bF`t(x;*}*3K?^u.]ĺupuuLUy:Zrrշ<<<HOO/r099N:߉%;KբjͶ[[[ڗ4]x11'cbclA-BTETWιsKhP< FVG;Ne8Ѻ^u^ӌ*nիdڴiCTT (EݧcǎDEEyp텉hӦ DEE1x`]Ʊcǘ?~y{=]gyZ-mڴy* 1L`%[Ypp$@umu&& g* z7j+|Q"wxPVG?~Mbee?6mÆ # ;矓ȸq>|8ua޼yL<.]Nٸq#;v ::iii$&&rUMIxxxၳ3cƌa鸺RF f̘A-ٳ'`ǰaHKKcƌ;o !D ?=Mxd,vr9|u+V[ΨC4-ﲪәypuar&<Ӯ֚2)+DZjERR5kּ:vȡChذ=o͵k'22oooTN:zj^u̙CFXf ۷/i&FU2do&s?ʊK=Xb_dӹsg:t( ,1B fiY`eO-ʈEaK.zu{當ĥIalo=ŖcGls̹Z?~|ڵl۠A4h7rHFysڲxb/^|>㧟~qR|V=Ϭ:-Up2$a1a'u03`&u/ǿHVm胇%+.]F;vήԂBQ _[+SR3o |YtkZWz! 'P aYk|z/Adٸ9Q|rnLe]zʹix8x`0*¨xg SW{7Sc7.Ľ{B ?GaP{Y J(:}S_7%u1WSx6jL$BQYeY=QTT}>c,Uv%X~Le]^n2O5~˱+y}RfkŤMIE BTF7NʧQ_@G5?X5 tTV>/~7ǿ),2w/=RaYKi9,ϏL%l4jFvn/E" BT6wᐟRݛxaW U>)BdB$b ʺ i)=:ˊ.3n{em5ūF/DiP!*t0W KGU.L;ɂ?uvE > 0².Φ {5-Ӄ2u]42WT"f ŋ/x' +348!qV@Qhɥ+)UU#<&xnwiWI$oਵ⥮ݹv6rUT>f `BBZ2j.B܇qen]0dxtTʥ[켴 M j")#EQ{Vj϶=ꨵpBA:too"۷lق_&.Nn/>j6kU˺8QFg8?ɢgX"yz>-<K7 G/D3KgΜɄ CQbbbXj/DB!nS#1PqOleY0u9g># bBp"a1aE˺Iwl9z𭧸hjOhf^QScر;F#j*븄B4X;.zA-UŬ?c|8Zx/$q7pu{\ {[yOTMwg[Y!NRzlM|},.O-rFeYkۥ!:zXkԖ _2KSSSy7عs'"YpBQ]5An:8{&{7tT>K͘~6jƭ|^(b.a0*U֋=}UF<|8wcƌ]B?<zTQYԥK?8]vPö/z'?I~ǿasdLlVWzɂQ Q%DGG["!F#6Dhj7O~ v˂r9|q 9 z+C|R˗8,:M|‘ Q>%Z"!"djw ]_u|^MQ~:e]:+m_s~=Lؖ8$vaǬ`_D-K qGf ҥK 7kk"W&TJUCL+|hlbx:ry18|0u2Lyu Y;7=^ޚ7ZmB܋YXzu222޽{튢R0 eBTIGaed^0T> g6uy77 _J xk#qd!mf > +WI BQº17L -U:OJtKoþLm=\<˷_@oPP`@:LjJUH!)رcѴiSK#U@1B`7`W$o'!ô<ץ+]WMKΖ Y , ҥK !Di2a,8f$Yu313|˻uװ?$1]\ÉW4OM F,D`N4ɓ'3sLZha6 䡇*Rʽ ߏ; '@z&GG>?'SXf0qOOsZ&ζLjʀVu^!JYѣ T*"%!-4#%`Qe]>7\3"/Ǎߝbiv'5fTZ^!JY`8KVTp CWg).^N^j;&X N(`Q1C}&voL  G-Ddz{{["!܎ϖjiDJn 1?HY ˽x1z7uȓMvupBTnV6mwX[[iӦЯ_2 L!*E]`w8<9TGoгJ>;YaY>Ėv8 #=G@5xO3ZzUdBTVO>$IIIԪU'|@!x<83;OoVeݢD΅ KhP\wOO@ZGZRwV2d`4 7 !M]>j+x<ZtT.13nTeJ)xje@M'-S{08.Vʟ Qޘ=Zm:իW3|2 N!*'L3}3:| X:R#o ˺ m6Y;N4G5$(#f߾QFѫW/jժUd[5j$Bq7gvjns=5tTƨODF)4π1*Q֋=PQ !8;˲;BqG1_VPBK9Ƽyq0uyᙜ<_Γ3=3ά^4hp0lժ* JE=onh0HHHW^ R!5Cl{b-5=gU9kإџchsdUڧmW$X0˻=СCP~}X !Dy ?Qv7ᑩrYb˺4xN#l58 =򥷿*L|MׯOHHBDd`eO-T콼uiڜ~.p++Dyf 1bt:._Lbbb?ҥKiРiӆ{޵uCdž ( sΥvѵkW?^] ogρpBoݺG!Ds |Ô9è+ew1"_s!5lk0mjdd֪\HCkfBFٕH'D`6 ̙3=}~{rȃ^f SLaҥtܙe˖ѻwoN8Az߿y  N#66"ۃ̮0޶~NZd[pp0$$$DPPPZ{}/sӦM0rHG^^M4aԩ 4Aߦ*(ȇ͓*SzU8JXLGR*M')"*#浫1w3ifPYGJJJ<%%{$%%ORR]oq}.^X1+*ȢEܹ3jM67|s=Wq/lgff+z}+VT2dL̕ژ䤢aK40O9v?cË|~3v{Mϓz6m؄'ZxV*N;EUJNpf͚O-.zj~]TlA1/_̶mXvmnnnE4Λ7zl۱7J9s%9&yWind{ԟȍYb( ōIR3o'q۸+Wz5F!G=+ex)NQUi>qqqEʿjhZ֥)cWD2dL1Q]3X^P ?*sobEϞ{nUL ߲63Utl)*GUywc>c%ziӦ1l0ر#97ÇSN'OK.ӿ6nȎ;L~LO&MhҤ > :ȹW3fY\| ִj Z͛͒H!D ?Mh1-]?KFo3#g30ҡa ^ӌVtBRfٳ;tNBjj*o6׮]ߟHHLLDoN:zj^u̙CFXfMa @YfIOO}l߾'+:uDf͊wŋh4||| 8'@uP[Cס Q1O"6Ghd5 qwdvftmZSn0Kvj rJGTi8lr2=ȇb;J"m^O\\s+L\/@i=[ڿ=늻EJn l<u~jU[q5ḅ 15 ̾fjL:2 L!JpD'Oxr)4n1Aw'cّed1KdJbhzܣ nڄU}ӯf͚Ǘf,BQ6RS+Z{.a\̼bR=Y=Ĺ`Zһ k:Z8R!Dy`9rH[Q]FXX?\BTq~[_}6hhtq!g(Rzr9O(W.RRJ_! %-[DR(J:t믿.De݀/C_+x{}r*KŲ#w (a>,9J/_`˖-VQޘ% Ej5kbk[k` !-q"䤀z&|5*F6DDlyr꡽9 kWlv֨eS!DsԩS !Dςmnjn*ܲqCGn!,&)G1满Ij>ڐ4Vn !&$%%{_[1 !Ŀw)T%=PA}XUٯ7rngmX6 x15jrFq ܼyg}5kRvm>cF#o 6gJ)@{|lJՅ! :>5oxg ]lrRbTT)]$BRxW_eϞ=1[2uTnJ^^[l)5ĥYB|xkp.f^B%:=-83/Y8J!DEU,_={2~x7nO!MQXmKA.muTOY:`*r9CvS nLGgJ1/DekCaxUhذ!< L!˭$8@$;2r֮,eG~;/Pg;k&uo̰h4TQ&FHPFEBrbl i`eOMj#n??F(l Q3kcef&0rHZCyyy7, \~}F( [^+Mm/04H8LaM>G~Jw ;(+|Oô [8J!DeT1 =\#tqiߛRC)u6X@)z|3;KbscWfng G) ˗[2!z#{Àeґ7AǷ'eL!?y:J Nӌ.MPUu}BJ> BQSs<lY6( /p>ɚc0VUf j]BI|;!j@쾝8s,}0d7Qknݹ2WQ$BOWǗ a8N>ßݑNNAF_@ZsԽ1ke!D! 9< 2߃P]eс\0Jmw[B˒PQ~ބp{Svkx pklٸӡC|sB<]xO3ZspBa" |8t7 4e&t_99'FQڻ~2WQH(,}_bjhhW7qAN%y'Ƽ:X3-)!^XiR!I(H>aj AѢa. -) JŸ3KCUQ~O(!D3LW~} :p @^g睽}C jBHzLCjQ!I@!Dٺ^Ѧv>XӲq-->]Ʒ(.|]yOsrpBq$B E#k r&gVuybdcW;Ьs ]-B<8I/' ~ '~4붃&|cqxew=.ZԼm)3{$Buq.yމ95Pk5 \BT !<Ndj>o7Yտ@P[e!WK(eJ@!?szlɠCIX:B7st|kcv3BOxtn"$B<]6l~mjlܸ=$ V jiCx߁x:ֲtBaQ !僰H;gjw=k[+7sYyi`TVw{ s=K($B{3a(V\ Z:2_OPFŔ.'k|).]J M6ݻ׭[Z???6lPuEQ;w.kΎ]r"}ׯJ*'44HDx pss_Fӕ̛H=_0S?^\$'f2Xq1**4go+k^h˺gJ'(+k֬aʔ),]Ν;l2z͉'Wv walذM?>-bŊH||l"h1ґ{1%ag8z5bjyX4F!(J=\hcƌ ""m۶駟2o<2{lfϞݻ`ժU(BDDO=| \_|XNNNxxx9q.]v,\#G{QZ!*[a$8nZ,$EQw.%av:u33֝ĶPBQޔj鈍5ľ}gL:ȶ`"""HHH ))׵Z-=+;O?̙3 ߿y󉍍[nf瓟_@ףkLq+*s5&H4SQ夢h݋RhTsJ_[ X;[8^j3`I1dLɘUǣ*;)0%%{$%%ORR]oq}.^X؞C0h quu-mL6AAA%~XE`` %zH\Ih6Gu" *'b%G%x*\b_?yظ4Øqu1W>'dLɘUT@7774doyxxܵgctgꊇG>;Gբ՚B./Mi"0Ƥ@g!(FpjghwX} ;Z8=999$A"#2#D ZXU-hPjmKoS5U]բ5_)DIL1L$bH 2  y?ϓ^%!536]ػ{h >Ғ=)ĚRyGyݔ20vvvXVe˖Efe"&??; ~sÇIMM^'$$>F)# k^j7n4g{?%53mzq9.M_ZpOşB?W⏀ǍGxx8pBRRR>|8<#x̘1C]ƛoɐ!CdxgD r<1=PqV>Kv$46huױuۂ5PFD4j<(w\z)SJpp0֭0-lc-[dLrm'/yy帒 8i*QѾbG!5)x\:¥æT;%Rl;uyfw=Z TIkn/Уh8^NE[!! v·Aa>8CP"h$p!VCjW[Utl~d$%!N @!MKմ] t*x t"$r*&:^'Y%4hݜH?B5R 6 ys4= X"˻]y>IܵmiZ"N^ -{B<8)xT\'$i>[7z.~ٕ¢_p)+Nvñ; ?_GYY!0)x%nשz#Ѣiim~KgOl2w$mŞv 9j'pƯB+BQOj!%Zľa_˻\Ǘۓ3͑gh9?_ 5x>u`ok_}BQrQzV(l*[ Zٷi]n;=)LkY9/&k  g`@\\B!)(댅;~ Pѿ-%bDV?ڎFU]ݢ"ocXaTqR,BI(DYqִKJin7xn.8ObD~>t^Kʄ`_ַ|xtgx$]?uB.)(!Ugf@~Ųˁsl:vɼ]* z҇YyWyiۊ1MPϭ_W!D eͭ8iZ }@)EܙߒWS-5؛מ̵L;<=ViHDy5{ !({,9 ?7/Z_nR)Ŗ|i\@kƾ }ʏYys[gPõ]vB<,φw`m:E/7YhT?ʼ-KֆޡTofdro\shBQI(DiӦáC$RsB#D\WZ1ugn`|kz m8^uza@B!:)(-}6c8{ \C!{cpi-y?O1i(v^`4 vmXB!>)( Wa08Ǵ]<;+wS7 ol ~MM{z+dZq9H(lml]7C ؆$!Roln ^]gXΆ~Z}&^̿¬B4h؍GPչjIN!#B @!pⷷw? /,|k_<˭|@w'^o[]p>cߐWh#ضj[F5EJyPB!ER 0X?+o=7q>#cΰb9 LuTֈnWòY4hJDHM<ȰB<$݄0==W=7x& & +2Oc}7VZɂ cZVZD4I'e-?!VmZ%# @ˑn2#3%uSQuMwFAX|f;?&#ͽ]BQH(Dq3@L1\B {:=l}yF4^ c?l_a Riu%2,!)(Nc!#ٴݰ7<3*4;N_-y6ЇjPٓfZ>F 3 x:BNjBaDHXivBnw=hTl:vy[9x^VC&U޶N$^OdrlU.V䮟B{'h렱'ӓ@| ]dDN\2صr }*Leށyt'ʈƆk<^]κueB!/R W# %δ|#糘DfPAoKx=L`Q"_h}n2 BǙB/ClFLwڢr Y'ΐ @%GZп?Gنl]Ʋ#˸e@3fD4ag|B!{R q?l5Mvƴ]g6\wۓz+O=C qқ Yyr% -ZiHhAO'XI(Ľu6NCMަٽ+Y#5Ze!Uj(4.i TwΨIBQ"n_A;h!;`b_I|5y¯GFAF>jm~kV6>12NPš  GldVB#wrimIUCB U},gy0}M:yac󿻄.cξ9O3ӷ^_lJ~\B!=)#C.l m#4jR-'ҙ8'/ov|- N0w\^_~ O!D'4ivNuT =p:cWi҆j懽Nk;wg홵(Zz20<<ʰBߓP[W!j2ƴ]A/XMHr7`mB*v6ʟmj]ɹC Yyr%F;wfd?a !Ey(S ϟO@@믿5~ժU b͚5ǕRDFFッm۶ȑ# <Q.1kÆ ;xQ)OC+45(?&ߚVe˛mL=s񗑛}s麺+c-}Zrf%şBRWwWXADDϧUV|<3=zի[ѻwo}z5kի۷oy̜9ٳgtRj׮|@ǎ9q?~N͚59|0C ֭[̚5z6m~ʕ+lBDqqoo!oQEXv~_1/m*ݥ.7jU]) -{78cB!Agfk̙37`Mf?g:vĉ8q"111̙3osaҤIe˖7|ðaҥ ]t1ȉ'X`U憗WI _Eyx: N7@GFV=ǜM|#L|.-k\aͅ_hOW{ZqBQhO||<&LߩS'bcc<'..cZܹ3s ))4:ud>iӦ 6v333׽{wrssUcǎ_1GLv)ޣfx#oQJu37'ul*;VtkmٛΒKXyr%y1-ፆTէBQfhx IZZZ礥5EŜ={6O裏*Tٳiժ666ݛe˖+N^^yyy, swG5#9d䡜<(/T&yx})zhHfհ7.O|s 0PZzDPPP#swĚĚĚRyGy%]6gM /*u):Uo!7dK"+]ШM}t \p,3?QLwB aǰC([{h6p3ΰhr yyvt %+E )' )ws5F3fE_kkkK)ewS⏀ǍGxx8;q))) >k-h8M<<1 !Ŭ ޽{sULBjj*[??ӫRRR,εlْ˗3ydyjԨ+k?FAFF͛7'** ggӝ(N>ӧZER={VKڵYx?'ʸk]ӶS<  J)%$_5}#݉% FCDZȆ '}dXa4ҨF&B2 dĈ1c[n/u9FCdd$E8p k #JAJ05"tGoיL[L3{+1}-^~:: '3NBPжZ[7N}E\T!xɻţybڮR0N^|<G;-C dSTr >?9g7} k8znpBE @)ȇO f&Vmނc֎\}0*hxY5t=G圩pԠ_G6JuJypB!DɓP-=̄]9z(Z{^*UJk$B!#C @Qz2º7Tiۭ&gg| M3{k{V͎5stq"\e{iB!xH(|صNC6hPƱͫ?:ñԃx3c-\܎ a$sNC8:ҧnOeʥ<!#xxk!j2d$piL͎ۙ($g{[ jS,92ģ^?h_F!B<G!HմSu.?|u; ZTN`_GrJ2:gC*t4! (Y7./Z=ea~ِF~E7qsNw{bB{=ί+Ł!)E0@6Ns0* ;?@> tWr,O1fƫR헤B!)Ń˼߃C+WZ~t`/2䎮 ݩ蹗ԩ< o}i@!(W]-1MbyaWy8myi%/D_y3@vZBQ.I(Vuez,.^g=5qu# 0(xCk-!9{ ]a0D$ V^a>o+&3FSZ=B!o&#>AIDAT,(xQUWZ`XzFK.ZUTSB!I(.7 l]ne>d-7jgy|s6{==kV-^ !.E3R߸oz :7afr5\ۗ>uPɾRi\!B @a%VV~EMHQsM`Kԅ) GBPe^8\ҴJvtuV(6]ԪTA4-B$ Z:|=_w#sy ֲ:u3dbC=C<־eFB<3ǯבK4amBYˊ+Р}* 4, !HX {O䇼^SH&̷^; 54{,Bb$`ydk\W;p`y\ro.PAW+^ѣ;-B&`yq!3|&(+9?NxP8/~ g;RB!Jݡ_NNh(D4BjWMxP8]b+ !Iy?F؟;l/ѦjƒiLf ![7|ƿ 0({^zz~WڽB!D)qQXNcs _Cjlc`= ;RB!J:F aӭ4f ]A@G``ouhNGB!|]Gvd]7Ǝdh F<2Gk,N !QqS"Wycȫj>h`TֲLB!DlEϟO@@믿5~ժU b͚5ǕRDFFッm۶ȑ#1ꊫ+\~"&!!6m/SLA)U<. ԧx;[`ȫFSH_lā3UB!/WXADD&Mb<<3G޽ ӫW/ve9s&gO?eϞ=xyyѱcGnܸa۷/`Æ lذn>EǎaϞ=|'̚5ٳg\2Y ̛Gۓr Pv89^_k;N^}ݪH'B+Ggfk̙37`Mf?g:vĉ8q"111̙3osaҤIi|۲eoaذa;v 6sN7oE ĉԩS\.]^'88'O2{lƍW&e]?˨GAAyat/yBqJ'>> &XԩEرc-uܙ9sDZZ:u2iӆX F\\EK:uM6zL8dG^^y;++ `״y?/#7t.FM3kL u=خYoqQ'9&9&9&9TQz'%Z^rB<==-{zzV9iiiwkQ1gϞ5xxxXaюUUN6{jTTE|nYWAG8h8w?g_lzDGGvɉ5ɉ5ɉ5ɉB{(R)uGg1Eg1'ܩo'Ndܸq,UFNpq)ŕ|"vO0t:]2 DGGӱcGo$'$'$'$'c>n?+JtwwGZKOOw]㽼]:oo;\tɪ˗/[ux^xd|N+oN:Ѯq;֭[Wm?$$'$'$'$'S>8Dgbu[9::-[yNXXU|TT9> ///|bbb1aaadff{ns̮]̴ٶmz4,B8)e`ƍ_|ŋ9vcǎ%%%Çп_1cŌ38~83f`ӦMDDDdzL:5kpa#}^zt҅!CsNvɐ!C֭uLzÇYf SN-33B!JJw\z)SJpp0֭llWlْ˗3ydyjԨ+,f?FAFF͛7'** gggs_ѣͳwΧ~j>Jtt4oTTqY|O!qP&1#Fyl֭V^|E^|;h$221+W櫯k4hm#By(B!eB!B!B!B!B!B!B!B!CyJ)@VVVm0&++ NW?j$$'$'$'$'c>n}H p UV=B!qݍRQ}@F/F)ֶVΝťX~I>ININININ,|(q>>>ؔO`ccCժUK...^H>ININININ,|;ϲW!P!FFFFv'DѴZ-m۶Vԃ(kkkKG&!B3X!P!P!P! ?>¯Z]oӦMYf8;; /',b5j899ѽ{wΟ?os==|BBB'00>̪?e1ӦMCaWsr^ypttqǛ+ڶmˑ#G, <<WWW\]] 1 i|}}2e{@WZEPPz ֬YSr&OL@@2eF9qɶmxA[/K㿗`0oӠA\xѢ-')Q,_\t:h"uQ5fΞ=[]/;wVK,QVP>^y9fWEGG}vکFR*88XkN۷OEGG+5rHsgΜQj̘1ѣjѢEJөSs{n6lƌc_rr5vڥԦMӧ1ӧOWjժU*!!A[y{{,sL.]TppU*88Xu|<33Syzz_~Y%$$UV)ggg5k,sLlljjԩرcjԩVܹ$7|rssS?JJJR+WT*TPs1<9Yn4iZjԚ5k,K_J:'ׯ_W:tP+VPǏWqqqy*$$Ģ-'HX<j֭&LPJ=* P111J),N/_npႲQ6lPJ~ب .c[UffRJǫuZ\kذaE7nZjhզMsXso֭[hT^^^j}U}gJ)=?RjUcM|||hTJ)իW/եKwY>ϪAYٳzWR/',v/%vޭ'ː|ԩN:*T\x X}||65..`|||1;w&//0..*_;wf޽ 27xg}:X/9 套^Ã&Mh"$,iӦEN\]]i޼9EZĴi^oܹ3/^$99STvNZn͛9y$dt(94{KiDPbE@r"IX\rB<==-{zzVJzpJ)ƍG֭  -- ;;;*UdYRJ5ӓ\Rr|rǴiӬǜ9s PV-6n=z4[_jOsk)aߦO>ԭ[NG&MO>-O94{Kie„ @r";_ Fc(9r$bDZ5?Q}`YXnm< Νc̘1DEEaoo=912uT4i‘#GX`7Y_4'zaŊ|W|7ԯ_ >N9)JYYʑ`_h42|c5'hr qwwGZ/)==SQF?eVjE~~>U.2220 wIOO772x [[[bbb;w.xzzx{{d^zw.]jOcvNz-&L/L gر1'W/}y z"))h?(9w&`bggGHHiٲe)QJ1rHV^/B@@t:XSSS9|yaaa>|TsLTTzsEhh(:L}$$$pWhh(3UVV1119d]vim6rSTvNѬjǜ^Ya]:uM6fq}ruuUWV O>E.ѰaCT ,~T}Q jby;v(VO;O^* 0@YzrwwWǏ7<9qڿڿٳ3Z/%wﮪV8`37//͉x0RAS~~~N5mԼtʣ(kɒ%昜5rHUre࠺uRRR,9{zg\9rJ)uVդIeggՂ SVs<䧟~RJ׫u명 Z7wU^^^J׫zJ%$$X\zUO9;;+gggկ_?as!O*^TddyiV\ԩt:[ZjU .Ԙ1cTՕ T&Mqɖ-[1`Tstǟ[lyls"F?,-B!k@!BrF @!BrF @!BrF @!BrF @!BrF @!BrF @!BrF @!Cնm["""JfJ)Jʕh48pTl /PBA!(M6l`ҥlݺ@KK-99Oƍ?cd!DQB< h4CD :/ZBl8h4n ֭[h4lܸ&MO?Mzz:ׯ^zЧO-_PPȑ#X"nnnLǏ'''7on.ҥKX"?3AAAzΞ=[Xcbbx'x{{3a #QFF9[jGG}dqߟ}RB|||O,b233:t(OsAH7nŋ DףbÆ nڜnݺh>/ &Mhh۶y|ѣޞ֭[g7o&44GGGZlɉ'1]v8;;BHH{cބeBS˖-ɉ]v1sLLBtt}ɧ~Jll,ΝW^̙3okmU-[ [[[vܹs_|a>ꫯc/_ΡCx饗ҥ N2dgg3m4 9U.\@׮]i֬d||)SZ*ӫW/^~ewaҥq~! 6d߾}L8cǚsg%--uOӦMi߾=׮]3qij*s~-ƍǞ={ؼy3666ݻشi^q?UVl2G͚5ܹ&MG}޽{eРAcjժٳx&L@w)D !ʝ6mڨ֭[[k֬zRJ%%%)@߿|<##Cj˖-J)l٢i&s̴i :wlqz)hoz):}h4… k߾8qRJ%K(@8p?ԩcqy *BR]۷رžzKT.],bzyR7oV...*77"F?WJ)*NڟtT^J)5`+yt믿6W>>>j̙JL׮]RY-]B}rPraÆޤ?P;8::hhFc ԩSo>RԮ] *bbb,{YᏎ;FXXŵZj͛79=رcjb_V}8~/,,cǎ7ob\IIIJ*$&&ҷo_qqq1?MII1$&&b0,ơx'}y07^{:0}t !2 Drꏏ4w3 ڎFkh4jGZP]QRV1gk;yFooo2VbE?sTVEh$88?#}vZ֯_ϻѣ=EQ;P}Ź>Ν;kժVI&N͚5-:AAAZk8;;{_l߾b_ll,k׶(RWݺuhڴ)iiiZnK\zcǎ1ydڷoOzȰG5kb{R^?ɀڵk3vXٳ'K,O @!Zh9z(۶mcs7n'NoO>a̘1ׯg$%%gf̘u:#Fܹs5Ǐ?2nܸZ2;7oɓ,[O?7|"nǎ̜9'O2o/¸q,b<<Hxx87n 447RR{ʃVիߟK.NϞ=y|!D١QA!wODDDz͝B܉<B!(gB!(gB!D9#wB!)B!)B!)B!)B!)B!)B!)B!)B!ʙp!IENDB`cykhash-2.0.0/doc/imgs/unique_time_comparison.png000066400000000000000000001520241414255425400221450ustar00rootroot00000000000000PNG  IHDR5sBIT|d pHYsaa?i9tEXtSoftwarematplotlib version 3.0.3, http://matplotlib.org/ IDATxw|M͞#!M+-F:أ(1ԊJWD5QU{W֊ȼG~_M"!y?9q}>9*EQB!DWB!K@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!BbF@!{.U*ERHR1lذ߿JO?THMڵ ̗){_E]!D8|0*T(jbjӦM(Q!D%TF W^QWAbMxET*N<ɇ~H%gϞܿ_+mjj*cǎ3336mʑ#GrUNƢRݻ7\z___,,,ppp`$''kpTTM4{{{BBBXl*υܹàAPFFFTTSc}Q:6lH5?# 6 333۷oj-ZX[[ӨQ#~gGRxι-ZVZ( },[LXjj*&Lޞ%JЪU+.]&""???*T +WfРAk>ccclllhҤ {ѩѣGi֬͞=;L/?Ν{XYYakkK߾}yֹY+/^]vQL֭[u솏ڗ1cTFFF/_`[n :up9&O̙3gسg*⭦!^ɔ)S@9r5k(zjEQ… 9իW۷O}i~(+Vի(6lJTVMkL2Eﯘ*wKKKSW׳=7V{ XXX(7nJ7o<PΝ;mSSS[[[% @kرc###%>>^+fWV~WP&Lc:///Mk(QBILLC*ejܸqC-[hYXX(/mDGGkYҶm>}31w\tC QLLLZ2n8ER\֭[/|;4۳fRGj駟@ٱcG 'CB=zhmw 3tMRѩS'}uƍ9o>Zlf>\m۶ヽ=iiiO8p@гgO6nܨ>LOOgժUQti{= znذ?3Wu_:thFALL 2WZE^J{u<==IHH **ufgΝԩݻw8::pM:Ù>}:QQQfY:xY/)){e{ξ}U5 xzl۶ڵkum6*(N$5imPti\٥offfh366&)))sYZZ(K?!e Ν;/_^ƃ43.ds/N_^K ,iO2eS3fȲL{{TfM<<c,Y-[RZ51aj5'Ṉ}9{,N"<<^zi_zU'm2e #,,8~gƏϽ{عs+ܻy$/>>2eh˔)i>VFzxMk֬ްaiiiوfe8}̙LJH޽ٗ볬Ӌٻw/?ױcGΞ= :}!::~[ҫW/DŜ9s8ydfC/C122G\t)lj 'N$,,ɓ'k&?UĄ̞LK.+2l0Zn͉'^bbb}oٲ%_ùs8u&!eu]|Yg&uǎvK8@!^ƍ100u֚Yu֥[nԨQ={!Zٳ̛7Og!7nB^!VZ1k,J,#lܸ1_1qD~gZhɓ133we2i$&OϟgXYYiωq >jժDll,;v?K޽;F{ٵϛMZkkkqttyE#F‚c.\٦իWŅǣ( Jb֭DDDh{>>>Pzu,--9z(;w?uy300ˋH #ٳ'˗/Ņurȑך`/_N>}fŋuҳgO G}č7;w.666:yy9:uVc݌= k;xk,!V3?tIP,--ݻ+wJ=Z)[bbb4jH9|l/p}ҥKTRҳgOرcY677϶#R5j+vvvʧ~|7:c*d9;ÕJ*)JR (&LP?~(J&Mtm۶Mi߾R|yH)[}~z2=z4_57o,;"kLJ},,xu2,9,YAI'x'H(9̌;2}B B!(fd!Bb ,YBJ011AXZZuULeʔA___޽{:|rLAݻGŊ5=z4aaabggݻwu~冄0j(vBB(Q"w ΥT"""hݺ5wq&׵`u-r] \ׂ6]ׄ_*Ȉ |_xzzݻiܸ1jJ뜶mH>}4yzcwQJJ oӧy>WQyf~H5u;JE (!'Z##<jǏcaaQl,r]FQ߿ϭ[R !;BZTդ`bb"J>w666ƒ*B#7` Cd\!=!B37Fhh(nnnE]\ ں!BB"._\$e+¼yZ*8880s,:t*BQdSS"){Ĉ޽yʣGI#hٲeB7(r@ۛaÆ1l0)]4'NDQV^;{4߿JEdd$Ѹqc.]UٳҒ~uѣnݚ2e`ee'NJJŊ166ޞÇ窍l޼Yk5@*7ベuÚY ئky{{uO޽5)));cnnNÆ Cp_5[lsTT 777_ 4<==su]Mq"|Fl;IB( OSry}2Xr%DGGpB,X~ d(ӦMԩSl޼ׯk0&L9vWslÆ L23fp1ʕ+ǒ%KOLLW^M׮]i׮W\`֭8;;m6*UgŊ\v)SBQD,BXUz'p!zNɻ (o_ ,@RQZ5Μ9 0`V … ,iƌxyy0~x:t@RR&&&ѷo_ٳgV/`-tRJ,Ɂر#qqqѪU+ X"y>93f :t`ԩԪUWRzui\v~[naooΝ;Yb3g?ƍ|3rHt޽{r Ǐȏp琉\ñfϴkqM(ԨQ#.<==r '_؍46 ߍU&E]wt "SC}6Wij5 X|ŤM IIIiӆ6mڰzjlllm۶h54mf0Vs]V޽>aaa8::bll.]DDD{aȐ!|8p@쬨T*TtۆXZF__Ǐu#w\rhw5j2p[[[;ɓ'6l&_EQ000`:BQTR,[bݱ$ p(hܓT\ŪjҌ132(DEElWR/ٳqppرcyοFDEEmdɒ%pMtܙΝ;3tPWΙ3g_~pm+Wxiۑ6Xnzz:gϞzꑞν{h֬Y4i҄4] f9GGGJ(3gYd {姟~RJN!/Gc&x] >|-0},h,ݼyQF1h N8E?>+VȈE1x`Ξ=˴i#ի4mڔ5kp95i*W̪Upww'!!O?Tkٕpiذ!fffZ SSS_Z/QFjƍ^hS-5j۷oŅ CUңG?>#>>{ꊯ/Z~ۗ0j5Cu֚^ڵ)[,&&&:(YyVb)3F%jņ"KAAA<{ '|!<<5k2{l͛ej[[[l24iB:ud֭.]ϛ77oN@@cƌrӦ}ҫW/RJ޿L+V ((ѣGSZ5:wLttUOO[RL7oNQ֭{ !Da ]s{3W`Ljf2*kѣG(QBXRRׯ_RJ9oZMBB%J(!`ooo +r Ca_P6oLLLLUPrs/c|}}_'U\ׂ!׵`*Oo9cib\T׾BBQ=Mg~:hJXFyyPӷԱ&v)B!DPpw\Yj:fЕvu $B!ki /q:<M*f~W7(x !v%bOVSgdK@!Bt7ލps|hI(BnyrJ f…,XoPMƩSؼy3ׯ_ `2M0s1 ۷؆ 2e 3fرc+W%KhH^8x QQQTR___駟X`K.ʕ+l޼WW<5'&L`̘1PjUwNZZZisӦӧbݺu>}]Ү];\eWsNӧRTJ#< PTTV3gΰ` 9;;pB<<={hhBNK.dɒ8p;ZА+˓1cСCNJZz*իWI6̵ku:ܹ+Vhx7n̉'HNNf|yH?ӪU\EQ|)cERԔ0.u^` ~p ?glT.[f1%="K5B_׾'W\!=='O燣#x{{G:u4/W.5@C.\S+cTZ+++x]30`6mʶwUԆM/s EjժXXXh>ڵkZiׯ_ω'Xv-۷og޼yY9w\~6n܈/ٽ$z8J󤤩iQ,;l% c+{cz,Y_|۷Uaaa4k,4i׮]Ņ3fhn:n޼ 4`ƌ4lPɉ7nh;n8fϞ 04ZMBb"%,-{8NRRmڴM6^h۶-)))jL\ջwo߿OXX)K.={2d_|*;+*JLcgW^cj}}}?z BFj֬Izz:dZ͛73gg`V!y.c:̓')1C z6rw`H]Z@PʹoF f„ ]J.^H||̙3g^Z ol_rOM^,7==gjիGzz:ݣrZ;;lVT/`ڴiܹwwjݖ-gPΒ4%өÖGAύ7/_~gصk_5fIF֭  $$?@@@N}Oe˖3Y7o2j( ĉ'XhϧbŊh"ٳg6mZ1bzݝMfΝ;~+W̪Upww'!!O?Tkٕpiذ!fffZ SSS_Z/QFjƍ^hS-5j۷oŅ fUZ=zWݻWWW|}}Yf bll s2i$֮]w ) !Ds=bĺ{ @]5 hUgES`x c_>j ,2"LIIiFk6m߳<:۶mm[ֱ9sPtiܘ1c0fqijg`С|' 8Y&gٳ3ydƍG qV˗?P^=>|8ekkk-[F&MSlݺҥ_y@ `̘1PynԷo_zEPP^^^TT 4+V ((ѣGSZ5:wLttf9sA:u eСYڐHEJJ ]t\rϫ|WBwZ?߿scZ;,/*|6քSL@R!믿(_<q=9sLV\vZ|k׮O>$''km۶_<}rʱyf{= P~}J,ɑ#G O󒓓OHHxJО͛7qrrzE!11KK˂}" -Znݺ,XP- }]Nʖ-[t/|$%%Crjj*n{Ru-r]nBc7kЪ 3ޯE)sW3뚖_{S>Ec/ !!2e#1 2V%_йICLL ,[L`f/ȑ#5iԩCɒ%ҥWEfbԩ:wޭ{d``?~^5 SZZ)))$$$zمkrr2oLIIٳg/eQH*^z]Ob5=0SIm~ɹu-x7ñLeN;CU4>.(L2kQt=lm~]қkoԨUT<;F f0j(vf`6mxz 022zkWtA}YuV]Wccck )͛7B&׵`4%\f[Բd~:m\קЏu(eIo3R5.k/zS_<\%""Bk<ӓݻwk !gEQ!>ҥK{qAjժd_S# `رB|gC=׶cևXDl&w`.>S3ֱFE3C 2!Cdy,!.]ХK411aƍ9W~}EzBwӷl=KHεڠB<~6 3^W_ޘP!qrS>Ǐ3&zԭ`WS]ŜO@MWuQ2|%B8c)* ̈VU0j5ĬΘP@ҚvA _; !ot’}W BZ)_vKC痿)]mw8c:~@jj' L N[ 7VQTZ- *͛7i[<_f~e ڳcDRA4Oӌ΄3?֑Ppmڷo_$e>|-Z`nn5<{L']rr2nnnTlMBI ceܰ2YW#a'T Cs(@J9! ]{aڵkGHH-ȈSNeرcԩSEPS!īJHJeʖsl:'+Z_^rf=>3?fl[ڃPc# j9sPreX"3f̠E 6L+066f޽Yb V;v,JΎPsK\]]177!Cc7nЩS'J,9jbǎZy?~wwwhܸ1.]U{￯/88oooͶ7Çϱ /9rzabb;6my Z{͛7,ٰuV4h L:U#Gd?ZjQJt_~ݻw3o޼\]!ěX~uM'DOagj5[3?4ÎH)ԧ|8ϟgҥ3c }є-[ƍckk 9޽ˀXjffc (ijD\Lq'b|2&z$=rn0`/ƖW(r2\=چ^nt@4fEW_}ŋի...4mڔd>lBn݀޽{TrJߏֱ:u0eTŋOTӦM?fɒ%@ƛa>#M:1c^^^?:Q>M6 72ws*o )ѹJVILL2ZnӚ朶q-/_N˖-5lԬY3oΆ ?~NkUTƍ2x`MFRׯTi۶-۷og̚5'dYFfdf92Obm \ZԩSu7115k:VF ػw/QQQ:ӣGV\Һ ! ޣL|moT/Rd>>xvuj <_%`!RTUդafh`^TRSSS"##߿qWWWYlk׮eѢE:i<<}_E۶mY~=  B]<`zQ2}|zÆZ ǗÞ*}h1x ƍرc122I&ܿsѯ_? nذad' ڵ#G|Xh:uСC?JLZ*{FעE {<==Yz5gϞ^zg@@&L_~L8Xٷ 6̌>O>#Gftv튞O̙3L>Jŧ~ʔ)S[.nnn\/O?P3'ϸդ s%(XڌU7뗟[w`Xƶ}}_ I&1zh&OL5z{I4i҄۷3i$.\/3gkf͚5̚5K+Mzz:CFk׎jժi&m2i$Ǝ{Gbb"AAA[nԫW &0g4JbرWWW~eڶm˶mۈޣQF|ZC0rH֭Kdd$Vz|;ޗus/Kyޥ^%௘R)y]#Dh$$$`eeţG(Qֱ$_NJr VIHHD:7oɉGR~N)566J*qI܊{955;vZB\ׂ&]WEQ-BiJ:V_rW]} >>TZULՕ7ndylҥѣk$(~Ϩ m1sLP sHI̘9ǃ/ \[E4۶mBgֶ_K!DޥEgsk)I.15D#(V)Yd?((~{̈u'9W +2C ̌Wqc? c+h5NdA@!)#qLvT5% QԲ˟.B­Zf2$ Bq2{=ЬJumџX-c:| UZ~'BQ~|?~b2FzmWM*=p;R4?||UxI(B䣤tCRւU_'3&yWE:4a`[3xB!D>|7?Dzy:[C89΀#K3&yXAϡ^LD 7VQT<| J͛B£( +Ӣ߸x'2F,Tگ]= ϵ+ ; zK'^9B۷oӾ}B/wРA`jj ~~~\xQsԩStLMMQ_}USwdeHNS]͆_F4E\[M;AJ >,OE%CB;Y~Aу+J6m~:?~V^;D__aÆIxxO:E [ OGTט葞}3! B| kD'=BZfΜ9T\ccc*VȌ3hѢNٻwoyX+++""";lTz IDATv,JΎPsK\]]177!Cc7nЩS'J,9jbǎZy?~wwwhܸ1.]U{￯/88oooͶ7Çϱ /9rzabb;6mBR wxx8Zyl޼Y֭[iР&&&8;;3uT4Hqrr~L>7o @߾}Yp!^^^8;;ӳgOƍsu}Rә,}8vl֔^^/8,207h9I?)YҪjϞ60xg>>:̛7Yfk.5jٿrJFEtt4w4i֭҄[… qrr 2cDzdJJJ +? 'L󱱱aۗCeemxޓ'Oر#-Z`\~#F]vѳgO.\Hf;8A% ]} XjJE-zUJ-m(%U{(*h* $m\id$D+ycLs8w*~7l755WWB#Dy% +p?tfJF_viY 8THyO IKũm uε"nÇP9lmtϟ… 5j駟&''7xKyPٵkl޼9aiР ._~1'OO棏>^3' :nݺuٳg30uT}Y+P?<ʣ/_`ɉMkVkΞ=S^֭G}Ĕ)Sr%-bʔ)dddиqccbbXj6m*T,BGF%𯭧Thg/-oTQ ~=l WMe͇?c+#H(r9q9993k9@bcc9zhٮ9&g͛7u;w?&>>4z=ddd̛okFTTzbСyڼׯSOSp'NТE K }C3{ls` ;;Ls#Fw\z>@~7r<9sfe9@&nK 7zi 3LǮ`<Q$,A*GG>TFt*U unjC˖-IJJ￧gϞԩS'W.]i&VZԩSakk;> ߿?ƍ㣏>Օ{2zht:9>}i&3g9oF׸ww5FV(J{-{guF#fsɝ ...4hЀ;RJ֭[Nj/hO=;v,ӧOd|BgQqɼ3u8ت1k[AO@ie2<=l`8! @R <шZG `a4hGGG~ƌu___ڶm7|? i߾=o}A;z?WZ'ƍcܸqgO-Uzu?,666OW>>>,],sCC׳`Ο?ҥKꫯrՙ8q"۶m#!!Çc4ib=z3gx$5|pj5G&>>͛7gӡCx8{,?sՙ9s&|q "##wΟ?Ϝ9s8t#L_ݻ7'O&99dnܸXQ'˩<`9G׺{_8mJ]a"xi$*JEĉ6mGK.׏|Dpp0G%88\ ^Æ Yp!'{ \Nll,[ne֭\ﷴ1co3gΤI&z_ƆÇy~;wfӦM̘1/@nٲ%cܹ4k֌˗3gΜ\u :M4o߾4jlܸhڵkGǎ7oy={пׯO`` ۷5LƮ^7n|r<<Żs]v JtP8ѫ BOeY{>^QN/HݻKJJ |9{_~?sa֬Yyʣr`cc;wE>}ڌl(֠>Ui=ާ+q,}^KK@;;;ڴiCttt%E8Wq$3ݚ߼lP!D( +ćd'7o352|0 O@cܚS'$B!J[Z9FT5:ׯ/š` 'q~_Pv1E,D$ BRaϙ(sըҧ1F]wöզ5M_P!U |5ݵ_󇵤iMGl4‘$TP?4X̑ QvI(j\K͕j1cmN蓯ś&y\o:haɣVbX'$B!J(,n:Aވڜ^>n>Y ?}_Qvth74CH/E!DJÔu6g/4FL86M;MǍ@KbX'$B!JSygQRjQگ1=#l kLǕkdzO IBlO.ȭ"_lIcJ?hC ICPP P!D:ƄKگ1q-6LM5[&ylY P!D0l=VoZ{>}9x u.[*Bϙn4 0;XH !(rӲyc>}k079*?Q-h:n2ͅJ59b!IBט2ۨ>jz؎iWaTt ?F}K&h!IB,ٛYtG%֒nّh?_>4PiIv%$B!˩LXys72ś4!]=fqV[%$B!,f4*|).&yW2MhLH("ɩټ:ǍO6'ކ$q!^ !P!Dm=ԵǸV́> kapbrmxv4]rA !$BQ`9z>/[˅a-Wr phyJ0r!$BQ ǒ0ae, )T0zL;u'\SM<ܚ\B|I( Fwc^iFį^OIÁ@1 Z$BUj%.ZooohӦ {yh5kヽ=>>>[Nwgggj֬IHHW\Ն**ԩS !DYtNÿϿBoTΖ ]_vL_xf_9B{_cdd$'Ndڴi9r.]Я_?CPP=z`9p>|3fpa֮]ӧ4hP>C^j>}zW!(+6B߰HOTvgoj# 2Tk!;V !T ϛ7ѣG3fضm/fΜ9yꇅѻwoBCC _%,,+VBttts,X@ILLv+.!=ws>5MKxUgg~ ;f.4ɣ:pB@VˡCUϾ}='&&&O>}<>@jj**ʕ+*;w.UVe˖̞=Vk;BpmÚIUf8/a;l{ϔq{M:K'Df;))) r9ɅԩS>|8*U2O0֭[SJSy-e_gVOhӦ 2\=Ϗ\w𢢢ԩ^wvܙoBwG+>Om}؋LxH״l3:OԩBXPK<]6aŰk2Am OOB-ll4&QWL<`ڶmK,!11qBZ3'L@׮];w.fl߾{ lܸ`cꊝ111߿ݻΤI4hPYB-&,4johaۼ?`DNgoިBRqM|͚5cԩSD- کS'V\ә1c#22:? @˖-s]kΝt {{{"##5k999ԩSc2eʔzBQ#_r;bTՑVS%wTC;@*Oh9l( ?~|ڵ+OY@@BQ^u߿q !DYtf#c9xA>Tto(LQ '.ܽf*k1?jVZQ\JM()š×yq2*:_ m8 ΪMý]K8U(p| l 릲V IDAT#Gj Bq%fm'KgіO}:ނs;L€0lV% Ba3q;ի-pk^ ݟ>4L+pZJÇ͋9 !(ZMaX`Q#vD1q"8i: UY+d!D)QΝ;Ⱥ0~x c'"ϣNdyVwahV[}8a:v} 2CrxذaԨQ@ux B`gX⯦0Cm?냣4*dNh= z} <84j8==!ȟ(,??7œ3RɖCT98 ~5WolQzA !J-" -Dzi. -Q9|r~o#rzӹVg*Uv8O%'i&)S\2:uŋEBw'pO\Nf_noJ._= ;gz=a|i+7IDuW{p;a홵d?ŋ… cƍL4k/K!G'O@KC ֊&L!TnE _~ӹtk֬jժ:t_|HB'ݎxg1nfhQ3&wN&Ǵ{óA/+DadJVZTH@7#,,J+W… Ϛ5B"[g'1ؽ"_؊xl@@q9!ʔswstHʞ3}z{5q_5Xo0 ^C1*ptRsRY}z5?Y7 m\o0>ԮT!|8ڵ+N*p~~~8::ZB#Sp׮]B<2z>xhZ y}蟰eRE/h2P&yR/z, F@* hAMak+KvB?RS2P.^SwlV; ڿ =<'J1K; 荣N5;1g~5l>), !D3>Q+9ߑVG'JM;yjc`xL]&ήcY2&`ٺB* $BQf1)2o0ȧ sklqB0zL|Z5~8O&]kړޅ o2jլx\#EdW ]'Y:4,xgCuBo.>>h"qppM6ٳ׬Yn:k:w}___Y&!!!\r%Wo&88\\\Ν;/xe˄8d`bR92x‹+!h$Tul>aʶW_}ɳu;믿Nvv6pAVX9so ^dd$'NdѢEtܙ~Oy7!((>!Cn:ٻw/:t 33Ç3c Zh۷8q" ?03|pغu+*lذnB<]ń\A9>'TL Sҵ9' ƞA#E /^gʔ)dff2|pjժ6lXۛ7oGf̘1m6/^̜9s wބʯJXX+VŅ\,XۓHڵ9q[neto???N:EF >OȂgY T9 L;x Sܽ²X{f-:ӣX k}뤦R\ sбcG\\\طo$Bs73yɼk16[(=gBWd) 'b4Ŵ"G]05VPVtjLJJ 77\nnn$''{Nrrrggg3uTNJm䗴֨Q䐓c>NKKLtCk-_Ǔ֯? 8`l2CBE0M?IҢQ1nXʑG8N5;Vhz~- 177o2sLv1s ޺umT\NJ)NcذaF-Z6u9s0k֬v]2qQ\wh{GP+ZZ~*Zh/g77Pms:w#ǃil=ժq~̴vVgQ8rHΝ;ѣqss{h(ժUCvNKK\mNGtt4{Vfi$kz~[n(* Ǝ}m9xLOBFe_SR<ɏg~$U @Eۊ m0a QɲGZY{#xE ޽{ٻw/-ZxhӦ 2\E`TT:u2KΜ9Ν;Zj6RSS9x ۷j~}ֶ>vy&Z ?'yz\_R_I(lܸ1YYYEɓ m۶d7n!!!ԪU<#x„ tڕs2x`֯_ٻw/z>ƍ1 ;ѤIرc20 B#[ʇe1}lZ&g0 Z. N;s5~򛹼yE=#Q,J-ZԩS9s&͚5˓Iv84((7orU5k͛S[SN\ӧ3c իGddyFoRR?3-[އsΝt ˗oAXpabBMQm]UM۸=ƣ-BΠcsf"#8}4j{B(Z%+W&55=z*7`03~|_۵kW兢(+˖-+TBN7WkWkȗy椲j~87nhȐCd$<xRY1;;;~Ǟ"%)Jo-Ļs dWKiXzb)?,鑪5xɋ\(E 9r<+'(S^fٚ,QSsu_%CQboΎ(FViȨ[LN%âm۶\tI@!D7d 4k&2+ި_X1sӵ&'e$M87x &;gH7ty7wyWi2`+C3Xvb^Vmz nL*(,JxWe*&!DQ3)|_CA%C%g$Él_FA kB6ZM kLCF "gT콼p&4qk(Q< j!-aNg뇭y4hc&u==y]7f㩹^ell;*CHrpn$F4* ^EjMW#995js=<(o3#>ԇc6ӈ2+ͬ:Vr+ζ4`DxTrBL@јV=KDR*[?^J|E9z6ۀ֨كMF0P*UrBEFDD}rVʕ+ )ar]#NOS}{DNdW<6EQ=wٝ\޴jSF5E:Q[kT/ӷo_jԨ<==_~Y@!DKņogoSHuEejvhulN: <B6p(,JwIIIȐx:uԕ2V9*ه×cek&ʰ4mkNa\˼Ɓ3H\EP `VPTT*z쉍N7 $$$зo"R!M[7|$ڨņ]gQ2+,ve/c홵d3PH`@8TrBB%fҧO*TCvvvxyy1tТP!l-}ɋSHȥigDgʟ,?1*ɍ+''ucD B]J} XB]N_f6TP'^|Q̈1 츴%KHJ4y45;}\QFYׯ_ϳ,Lڵ?2!߁ЉRi?I|Ed2Yn=◑nJl6OO\Y9B!JE 3gxWطo_{Cd!h!RF6a2vC_6dDr#+N T$i4*U%MKM+G(uXKذqF<<3E<ɺecq9 XB^֍K:z]juaTQwo/+Qaݻw?]ZgL:LȹVѰX(EjZXΕ+ةXo !>!ԭ\ QYv-Ogy( Y-v \ГhΖsx9p(v6ċ0IHfxGPž #QUZ9B! %ou8r3f`E uQ!a ی?tr`GD\Q+*y4u`#{ Q,J]\\{{{&MġC;0!a2S!3 Kۗ5j2bdwn S]PšHY^:N*&e`<5ʶi8)!߯k`KèZ +gY iQǻ!>!T;B7cǎ:VW'Т.Du؝VC;ͬfMVZIHn%`[&qwvrB%-[DR(J;I`B2a+GaV2ߡvZ֎LXɹ;X 65j\>#y8:[9B!\jի  Qn) ߿=;E%cu>r¤Cvf(oE@{yܷ/!MCU6jEkh,N:~<==˗- dѢEx{{@6mسgCYaݺu^_v-}ZjT*bccѭ[7T*UaÆYZvm EjhKׄ$+gtm pc cƲ^Tك,ᅵ^}%ʊadx ׯ_s###8q"ӦMȑ#t҅~oѣȁu222ܹ3|C=vX^j èN*>Ѕg|Jw*;Y;:QBRsR軦/}NhȰF0d{̧[kٵCRPs:QQQ2uT>>MZ y=z4cƌ ,,m۶xb̙~XX{&44P~WXb\pvrr]<.m6FyP6vt$'2֞YK> jx8qϻl {޽QFuV&M֭[f˖-j9tSNUϾ}='&&I&*ӧaaaYlnnn׏+!ʕTXpgԘfՌ˱ԪbD KD|$Q1РJB|B;4+Ti&ЫW/ƏOiذE=))) r9Ʌ #Fwww?Nhh(G%:::999䘏N(:P~{ukfhw.U4|źGxU-Bj0ؕ'r,ˁutHy IDATp`:w4 Ag,1秴듢,kY*r >>:֭yqEQHagر?7k֌ жm[>L֭ԟ3gfSS]PJF~Wk7YˋAUVIkkaaѴ~ پ3>r-lje~}R~̴vVWh4bkkk>h48;?MժUC{wţ,ki$*5jT#GI'O&88mǒ%KHLLdܸqPV- &еkWΝY~=۷og-6z-r ybwww9w˗/TVxz-ZjEΝ} wxṲjB$ApUl44JdUuUT^QWͮX"EA*.JPRbB a 0I\W\y3L&<4iJ7QE㴝MS1JD)#[Y);7Sruǫiu>PQ.7l &Ly')**l6a:w @aa!Z홒gʕvjݶ{F\)өMwXo&OfLB@@!אgvN1I[ES⻒x;mڂB:ie0LŸȻ!߷0?t~Me'@:oMl2-qG\._!$B!_6= NG'z7xgeiůTi=,wUF1#0na|$5 g|;GE~JZ8@k,bY2YC ֦w zN#B4 ouiʘ<=ڌ ecY,^Jt @nLMuM@!Rp(фsn5=xrTS,r/YKٗSCjp!?P`)u@G˙[};!mX>7*CMEu{N;8^ghסLKF\8G(h $;kשJV2a&_G(.c[{gBB;qqBD@!s|#QGvN,Q/g Ox 6`w bJFwMA t !.$B4G ~]'gbѵ`Dn}'(~REYm۠}<]2q]i%yBz ܜ5x9O&&wVPig 6vAõr6zqBB@!s|ˍZqTw~ץԛP[y,]O?dTQ/jϔ+`0VmB# 9C?gZmT´~yR9TqwrýR]`;&Od\8AlذQ !#IhO(ײk1uutctu?|^9 @lX%NcXat}vݗa !1Ih=ԁ'SqQ-yer::JQry缝6?exi\ )-d$):gwOS4 Bӆn6!WcPebힵ,]ƑG0h )to !$B45 .o1Gh}eW^'|yYJKKcK&-q6#B3Ih*t=|Vɠ)L(^މŷ !D=P_;g3^T5ۄ^\(J[%WN;mLm5VO+S+G(G@!|!_¢,YJi{xMCv!`Y2V FuQ'[ !Is|xZ7o|Q(#ya\ !Fm(U,^ݫ8-Ą03i&CŠBKq3[L͹䮁18Yon:.˄6 ܞt;tFvQJaPK!g&űǭ10#mOz|v3\"ǬYkO,,9X23Ȥ:#LM7:fG@!.KzcEREڔ&ǁ6??Ȓ%j]*f'ͦWx/F&SN'w%#pjgI$B4]4ϐyȕ|#S:$ϱ(^%K[]oH!LI\8G(PkL32de᪪jkۖ䚯$LfmE /PTTDbb"iii\ymfϟϾ}駟fў?^}Uvի}<+cѢEtر#u yC֑ cc=q)_~d@32f$K ?!.ɓXޕṺ(-j "01Sr)&'KQ$Vbܹ,Zꫯ2l0rrrԩW۷3az)Fڵk?~<[no߾TUU1`ƍٳ|ܹs~zV\I6mӟٹs':Ah%vߤ;VPvN92Ǵm̈́z+7˴iDG8B!/eaٽ'bFϦa%0)}e/9cL "|9s&f -->_~ zOKKcРA̛7yeXbSLu>fyy9:_=˖-#::͛73dȐ~_V ~ R+S2ב6yV~Yop{H=­q29a2M}͋R {ag%#Kn.f ״5tNjr^cfsN~Z̶m۹j2diii;wn3x`ϱ:`6ٶm$);ACX4&`L<=ڌ x*{Ҝ>@kSk$LaB 8B!lj5^M—ܫ., ! ͣ }6>X\('ǎtQ{OqqE?9hժNtՊj\QQnny_qtF7W90`!w9@~ Z ГIF UcZn-gE V^IE>q@Ð~kn.,YYX23p9N@@\Ƥ$ 8~LO'_?Ш1v<< ŶPt OxߴiAA S#==A_Te85n,{5SN XgYQC^+]|mo`=Vۖ+WbHAOhn }au\b*rbmˌ\}:mJoQR53Kf _v6ګ];LII B/lJES#x `@@}!==VtFU}Gzzzy6m:oV>}`0HOOg?_}F#F^ yn|0~$~H|Pxrƀ<4'F}NRV_׽e{y=u>)NW^N͕QW]yh͵_,533pt̫6(II` )w)<2e _~9^;SY}[n  ^=z-(S{KY9ysctHI>]^(ߕ|l/9~}뙕<6>NC\Q5'UǂCt41%ģ55(4 55:o/;cǞ|vv/>d⥗^⥗^P?:VOwokƩ߲؝6r=_nt׭ /Kc؜=V[* w+a0G(DQJ8zLwWZs,$0)ɳPd6 AH(ٔ^ :YRFq[.hrT*Xe9(.1.vAIE,/:ӽ;yW;mH$gE))CD"J@!N.uB:ݮg>djNRTBpg>βe[II:tdF FŌ"@. kn27aI6؈K  0@o8q̊ܚΟjm⬭ӒCm$i:v t}r&0}@[e%|q ϱ?DYILJe`ګ~W;1!޳F`r2Ν$:5Gqh ?rs)[maI2e3t EcQRRseϝYp:V׮-)Ift-Z j!į! hZ*špp+1m[$o+7X?3rs8!}h5[ez>O?yh6>2Rrh$Mûq5&NgJwo.Կr9m۷7P{U![zɭ. IDAT !pQ7eaYQN16I,:K+Ds$ h6 OrURS@4ϏH`mv~ջW4{)ե2brdn2 RfU%б;ѫ)bG胨$q;Qgx1\5ǥj[YwsgFxP8ss 2O_Ǐq K&rv0wD`J2$[ b!I(հ~.*)'lwE{]f7cx'V寢5:4晌ANVYWu5ZGx`JHpMJ&0%Ct_]-B$U'slw&+EPxi=y7d޵Xz <V|;tPTL5سsĺgw nz{dL=?h\Jp,^rĿ\csmw :#lp: 6P%s{\*L)pZ[Mj]­Iх#!DP4J7QqX(Q-k#%m~gm֜aI6܌BЯ}?f'%kfTgfʠ:3c^\J!(!" !IU @·h/)<`SxchͻΒ,\G6Zf%"]#eas̠zWbck l1H(|w)/Įt<縅 ǴЌrSJVd.it :to_K\t'z5+r(ݫS3;i.b2yn퐟)B Io\?Վ{s@>HX`t9\%K;Ak71l?Af=1Z1=ƳfLJx+-܉BLĸ45-Mr8[u^i4Ԭu_3F !FZsh" ʰaɡSN^o΄ xꩧ=z4k׮elݺ}/[o_ D~~>gKx'={琐V}8 wP=zTʭVɪU','hؖi s`G(晷µdfNX## LJ,0%& ߧii /̙35kiii|g,\Ы}ZZ b޼y̛7-[Ɗ+PJƣ>7 ҥK`qsy ¾l ]ljR؏.Ò%;ɓ^m!!̵ ,"d^<lܹ~m۶:}vZdž BZZ笠{n7 8m۶J{9z)7n 8O|Պj\Q3nWգ1J ^c<4c"ژ.k~u)ۋ"ۊμfcb0!` 5׫kv6l\ee^4&Ƹ8 ͉:wF]|R>ܯMkhJblh>O;$¦y_l{]mΞ7gzMVo7ogɒ%u>… y'oڴL9異uٸ|h_Ie" Wfw_LJqGZl?úc.w_ z{؏ntC!=i/_ڪ*Lu}+rN52KtG,QDwjN溿_ks%0B:u!Νya9*brr2ZbرP!V^źu C !6/^'`Z38rĻVK@nfL fMbf>I6ԯu|07>O۶mNWZZu_lzN_qq1۷M] [gh41p {7݂ J0GA׶cRE;x7]:g.-01~"bFdhMIC^]Xrj-Ұ_gۀ.]0&1 tRikh |ЧOkqIOOgԨQuާ_~׺i&@׮]$==. p5ܲe =ycj%W[{+_pJsc̻c]Iw1NOq,]ξ}WF]ɤIF gKlXv突YӫCwdvMH@WW9yp3e.rkFaa!wy'SN%**ʳ"xΜ9\uU͛7uV=;w\yzA=xg bĉ{!Ɏ;k ocȑuaŲ|ZGyΙtnӴ#'2o%kV @>ĭq%ol&Áu_{En{E5/mthNdNt'{R/k 8~8O>$EEEf6l@Ν(,,D{JrJ{1ϟOLL Vx&55SzӦMFUVO`Zܹ3gO9qX;T xMt1⻒X/K v  :r<.IZ{^maagrЇ7zB+"HMM%55۾Kccǎeر=Fa,X{͎;~M.1ߌEx(y{H, |»yﲧ3)~ ^$GI^vVl\^mAA1%%hvmӄa5P4a'Xɝ e#EUŬ[=)yGƌָ[iDZcp|~l99Xq8Nc4bÔtf(7kWZ{B!$q9| AaU5=ƒwIF)88y/SDDqkܭ&Œa>qsaΩHQRBP^)6" cGB4_ilĪ |a/:|SyyPWȾLiu|?}*=OD0:ZJJxo{޽=#bF09q2b}aXYjkܩfE{G6~%7  P\< VgA<도7O)ŮvS ,W..7{ۘ0!`5a^Պ5/" ۾ڋ)IÔ.$Q !hn$\.Nֻé4ş+63fU*k 2IHiҠ_ߔݎu^,W[}ddMٕDt-[ j!@@Q/%N!şᒿeY >ԚcsA E\.lĒZZYNLD 9 !h>$g\{n${S$?ɗdyr)sL~_"|c>.*VFucR$. %G+{5CYY8j11%$%tFAB!I(@)JWKx2\JÿZKW`=9s<ÌamҾ^rUUQ]> =W̞ؽ;?_X!D! s(]~;kq) /jG5_ֳ6 u \jLzߕ0qlX=W,YX6KwQ$P)>m`B!~;I%8Ҳ}LO}_p)}\*{6s3c30lѦNlq>;˰{##=]f{v\=WNseԕUj-J,g̺50\KƔd"cB 6B)IHٷI*Arrܔq|c6nQ-] #`H!4.knz{nxI={5ֱ֑C!7^$|hQ|=n{+D}|>. (.fx 6N-:[|og^ufyM3t^^.$bB!;I;yop|mBTagO0cC exᤴKsR = 4䠪Zv/H:"Wߦoz|!I؜9>NEX:zOYv 3 %5[IU^NlUEMB!$͔:UWnSؔfлWNZΒ <0cE?}E܌L^4{,]ʶiB!% `3d;^FmO 4nJ7EJDF_㸪֚g?XPؽY0@V! NfؗL{qU+v]q]"+80 "8ϫv{ZkݻNNjȭ6(B!~I ep"L5ث:r ,+3h L:?ra;pГY23塬VvjkNDߪU?_!Bz6qΪ2}DEgU BXӽDK(p۫:-;Weci[ М^^k+B!h|$l,eE]<] w*o#dV^oL10Ϡmˎ,JVaƒumL&LZ{If ;ˊ\!96Og1 9-E|m9mO陬kOuF1{FhB!F~ѢE HZZW^yyۯYo>bbbx=zvO<eeeۗ_$&&zڔqn:FK/D˖oa<ΞE`*iko ~;t[)!jK,gLs5'>cllۦ !Yj U;w.-b 6:unlL0zѣGvZƏ֭[۷/?|݅ovL/ݓ?24_{$:C*-lŊ瞡4c0%$b211š 3B!Dc(_|3g2k,xYpW4 ļy7o[l!--+V"--G}oK˹;eƍرÓ4.^~OϞ=/ѳ #5vJ: bOEЩUwhSbg4S\KBѨ<lܹ~m۶:}vZdž BZZ笠{n7 8m۶qw}v<W\AXX۶m3ZX*RQf۱/盯9ݚRʡ{\8;Fԋ$L Іn81>_BH6 ׆є)|;v I9D"""(..>ſOO;RK\HզΛ7sEE <-Z7֋9C&={uv; 4p ׆!0_FS#x `۶mt^WJKK##Ei߾y۔x~:Fu5 bOr$=Ɔ h٩!5E;nү CaH6Я=KAӧ%t_}~ӦM]v%22VƖ-[blݺpΝ;gy=zУGy8q" :ٳgꫯ20Ç `!B( &pq|I0lذΝ;PXXV{beYr%='&&UVZR]]MjjM<5}]^j#G=k!Bh @jj*u_z;v,cǎ=4 ,`mӺuk-[v !B4i>(B!.-IB!$B!~F@!B?# B!P!H(Bg$B!3 !BFHSm9u z?~mү CaH6ԯo?$ *9)IDAT++q$B!X: (N#ѣG E+**СChѢ^Ϥ_kÐ~mү )RJ:tV럳 ojرc>F-RS$0_kÐ~mM_i !B1IB!n |NꫯF$0_kÐ~mүM,B!32,Bg$B!3 !BIB!$ТEڵ+&>}!]2_}#FCh4>Z+X`:t 00Zmʘ2e aaa1e~Zm2338p DEEOz f0$$$vڋ1Xp! %<Sҧc…h4Ν9&}{,XF][ʕ+`P/V999jΜ9*88XZfڵkkϪPf&Lڷo***#< RJIևoFuE%''9sxK^\%&&"Wiivyo?62՝wYX\\z}\.}Y1Ţ+R*''GLo߮RjѢE*,,LY,O :(˥RjjС2d[.8ƪTj˖-J)~ge0ʕ+=m9ZڸqRʝkZuO+V(Ѩ˕RJ=*..cq+^Kc֪U+dzPYYz= ㏫:oV$CfcΝ <m6Ex߿Zc48poNXX}+{}l)D-Sa!x\Č9b\hȼ-C7SI he,-,lD6S ""sC'ZxYJHz9<9y"1cŘbL!aXU{{;PZZn\<== Y@@<==ŘtuuT1sΡۢ%cFFYYYuZ-1Add$f͚%Ϲ}xuuu􄷷7ϟ/{⥺ȵk`4R$U*ZZZҨǽOSSnrx|KK 4Iڼud,ֈj*L>BСC%{CB3FRݻv%c&UUUj}6O?G2={֤߯gʔ)oルWb͘6mjB Hd-/?r_ C]XTVVĉ>˱>,EEE_`ѢEt /`q۾͞=[|=~xhZ3ԩSGVdd&96d}cj\zؿKcXM\\:Bxyyj5ܹ6I|k[[͙\.ǰa,z[2kP(0vX %%&L9\.\.NÎ; ˡR8Ə:(.B@pp0%1mڴ4* Z-ϝ;whZ̙3bLII %1Ǐ, OOO Vkr>, "";;+%`0… ]pA˃=s9 EoKb͈]]]G*TTT?!!!_sn]WWjkkV[d޲TSSC+W$O{h/nܸAT^^N/rqمTrqql ] 00N:ENK*~:T*Z`UUUQvv69;;K*8y$d2JMMZJMM5TAc+V ***,!,_h̙f2*(( ///j|GTSSC\Vwc֭ǏScc#UVViРAGDoqnի.^HO9s搓8.Ю]hѤP(hĉr,Z["`ƍVޞ¨JMQQQDNNNEmmmJ %{{{RՔ,.SpϏ?HdggG/8p@nX|L1bccՕJ%͙3.]$駩"##IT+J{ "*** R(h(==d<-5Xt8777 ?"Իܽӓ͛Gb;[m@knc1\gc1l 1c6 @c1 c1Ƙ1cpc1fcd11\2ƞW_}+W| APQQPh4l۶111ѣGw^`0 i鉳"1dɟcI0 ܆xxx?g=/1(>>k׮+j5v^oq!EEEǎCPPJ%fΜV9r~~~pvvƂ !9ݻw!C`ذaHJJΝ;Xv-FL2Ew,^qqqtAFy`ΊR#G">>nz`|{{;ggg̜9ϟۓ/cϞ=5jb Flݺjo...?>nܸ!Ya @ף QQQpssRĸq㐙1ƞ\2D탃JJJuVlڴ '99_}܌w}۶mÇ#??;w49\.GII v؁4޽[l_d N<,TVVw믿:1)))ؽ{7n2+W7Ipy###7ol߾6m Ξ=kvUUUyPYY~'N@llx"Bdd$ZZZRL8ĸ9rGgDFFtزe p?#''999tHMMj`0`0`ȑذajjjp"==Ç3ƞcD4c >}dߤI(11 mmm PAABAܷl2Ϗzzz}GDD$]rE2pZneffsׯ'___ɹvEd4(--Fg?EGGKo4h $"ѣGSZZ/Loߖ3f믉hƍ4x`h4؈|}})%%]f M2Eܞ1c%$$Hx7iɒ%}1lgc@ɶZ[[J_;s䘩SBq[/hDYY>>>c0l0q[P̡ZhZɹ^yܼy/_ƨQ,cii)w߉===hllI͛7%N444NNNJL&<˨RZrmWX~eeex0w\~.1d[X}uwwۏ }kd2B&IJRRؙCD&1߱Ǵl2Ǜ+"{zz!yn!C媯=J]ٳg FAA?c֏ @ƘEAAAsǍL F"44H b899aĈ3qDTWWcرǷ@.Œz\* FnnnXx1/^PY @ƞ%ƘEJ%NTHJJzl777cժU?~ܹ DEEa…Fcc#Ξ=-[ 77w@܌88x 6n܈UV hɘD:u 111@]]:8f͂Vܹsq1z#)) ΝD%%%vzzz駟Guu5rrrL>f=dYlϞ=FHHo> .Dgg'&O!::Zl… zj⭷BII F918s &L˗p1N:"((6lxA0,]>>>?>z=T*Հ$!777\t ֭C`` ɐceY1c=/1c6 @c1 c1Ƙ1cpc1fcd11\2c.c1l 1c6 @c1 c1Ƙ1cӾÖHIENDB`cykhash-2.0.0/pyproject.toml000066400000000000000000000001021414255425400160560ustar00rootroot00000000000000[build-system] requires = ["setuptools", "wheel", "Cython>=0.28"] cykhash-2.0.0/run_tempita.py000066400000000000000000000012441414255425400160530ustar00rootroot00000000000000import os from Cython import Tempita def render_templates(pxifiles): for pxifile in pxifiles: # build pxifiles first, template extension must be *.in outfile = pxifile[:-3] if ( os.path.exists(outfile) and os.stat(pxifile).st_mtime < os.stat(outfile).st_mtime ): # if .pxi.in is not updated, no need to output .pxi continue with open(pxifile) as f: tmpl = f.read() pyxcontent = Tempita.sub(tmpl) with open(outfile, "w") as f: f.write(pyxcontent) if __name__ == '__main__': import sys lst= sys.argv[1::] render_templates(lst) cykhash-2.0.0/setup.py000066400000000000000000000055151414255425400146710ustar00rootroot00000000000000from setuptools import setup, find_packages, Extension from Cython import Tempita from Cython.Build import cythonize import os with open("README.md", "r") as fh: long_description = fh.read() #for the time being only with cython: extensions = [Extension( name='cykhash.khashsets', sources = ["src/cykhash/khashsets.pyx"], ), Extension( name='cykhash.khashmaps', sources = ["src/cykhash/khashmaps.pyx"], ), Extension( name='cykhash.unique', sources = ["src/cykhash/unique.pyx"], ), Extension( name='cykhash.utils', sources = ["src/cykhash/utils.pyx"], ), ] template_files = ["src/cykhash/maps/map_impl.pxi.in", "src/cykhash/maps/map_init.pxi.in", "src/cykhash/maps/map_header.pxi.in", "src/cykhash/sets/set_impl.pxi.in", "src/cykhash/sets/set_header.pxi.in", "src/cykhash/sets/set_init.pxi.in", "src/cykhash/unique/unique_impl.pxi.in", ] def render_templates(pxifiles): for pxifile in pxifiles: # build pxifiles first, template extension must be *.in outfile = pxifile[:-3] if ( os.path.exists(outfile) and os.stat(pxifile).st_mtime < os.stat(outfile).st_mtime ): # if .pxi.in is not updated, no need to output .pxi continue with open(pxifile) as f: tmpl = f.read() pyxcontent = Tempita.sub(tmpl) with open(outfile, "w") as f: f.write(pyxcontent) def my_cythonize(extensions): # prepare templates: render_templates(template_files) #cythonize extensions: return cythonize(extensions, language_level=3) kwargs = { 'name':'cykhash', 'version':'2.0.0', 'description':'cython wrapper for khash-sets/maps, efficient implementation of isin and unique', 'author':'Egor Dranischnikow', 'long_description':long_description, 'long_description_content_type':"text/markdown", 'url':'https://github.com/realead/cykhash', 'packages':find_packages(where='src'), 'package_dir':{"": "src"}, 'license': 'MIT', 'classifiers': [ "Programming Language :: Python :: 3", ], 'ext_modules': my_cythonize(extensions), #ensure pxd-files: 'package_data' : { 'cykhash': ['*.pxd','*.pxi','*/*/*.pxi','*/*.pxi.in']}, 'include_package_data' : True, 'zip_safe' : False, #needed because setuptools are used } setup(**kwargs) cykhash-2.0.0/src/000077500000000000000000000000001414255425400137405ustar00rootroot00000000000000cykhash-2.0.0/src/cykhash/000077500000000000000000000000001414255425400153725ustar00rootroot00000000000000cykhash-2.0.0/src/cykhash/__init__.py000066400000000000000000000004051414255425400175020ustar00rootroot00000000000000__version__=(2,0,0) from .khashsets import * from .khashmaps import * from .unique import unique_int64, unique_int32, unique_float64, unique_float32 from .unique import unique_stable_int64, unique_stable_int32, unique_stable_float64, unique_stable_float32 cykhash-2.0.0/src/cykhash/common.pxi000066400000000000000000000003041414255425400174010ustar00rootroot00000000000000 cdef extern from *: """ #ifndef CYKHASH_COMMON_PXI #define CYKHASH_COMMON_PXI #define CYKHASH_INLINE static inline #endif """ pass cykhash-2.0.0/src/cykhash/compat.py000066400000000000000000000005341414255425400172310ustar00rootroot00000000000000import sys import platform PYPY = platform.python_implementation() == "PyPy" def assert_if_not_on_PYPY(statement, reason): assert PYPY or statement IS64BIT = sys.maxsize > 2 ** 32 def assert_equal_32_or_64(val, expected32, expected64, reason): if IS64BIT: assert val == expected64 else: assert val == expected32 cykhash-2.0.0/src/cykhash/floatdef.pxd000066400000000000000000000000641414255425400176730ustar00rootroot00000000000000ctypedef double float64_t ctypedef float float32_t cykhash-2.0.0/src/cykhash/hash_functions.pxi000066400000000000000000000301571414255425400211350ustar00rootroot00000000000000 include "murmurhash.pxi" # hash functions from khash.h: cdef extern from *: """ #ifndef CYKHASH_INTEGRAL_HASH_FUNS_PXI #define CYKHASH_INTEGRAL_HASH_FUNS_PXI CYKHASH_INLINE uint32_t kh_int32_hash_func(uint32_t key){return key;} CYKHASH_INLINE int kh_int32_hash_equal(uint32_t a, uint32_t b) {return a==b;} CYKHASH_INLINE uint32_t kh_int64_hash_func(uint64_t key){ return (uint32_t)((key)>>33^(key)^(key)<<11); } CYKHASH_INLINE int kh_int64_hash_equal(uint64_t a, uint64_t b) {return a==b;} #endif """ pass # handling floats cdef extern from *: """ #ifndef CYKHASH_FLOATING_HASH_FUNS_PXI #define CYKHASH_FLOATING_HASH_FUNS_PXI // correct handling for float64/32 <-> int64/32 #include typedef double float64_t; typedef float float32_t; //don't pun and alias: CYKHASH_INLINE uint64_t f64_to_ui64(float64_t val){ uint64_t res; memcpy(&res, &val, sizeof(float64_t)); return res; } CYKHASH_INLINE float64_t ui64_to_f64(uint64_t val){ float64_t res; memcpy(&res, &val, sizeof(float64_t)); return res; } CYKHASH_INLINE uint32_t f32_to_ui32(float32_t val){ uint32_t res; memcpy(&res, &val, sizeof(float32_t)); return res; } CYKHASH_INLINE float32_t ui32_to_f32(uint32_t val){ float32_t res; memcpy(&res, &val, sizeof(float32_t)); return res; } // HASH AND EQUAL FUNCTIONS: // 64bit //khash has nothing predefined for float/double // in the first stet we add needed functionality typedef float64_t khfloat64_t; #define ZERO_HASH 0 #define NAN_HASH 0 CYKHASH_INLINE uint32_t kh_float64_hash_func(float64_t val){ if(val==0.0){ return ZERO_HASH; } if(val!=val){ return NAN_HASH; } int64_t as_int = f64_to_ui64(val); return murmur2_64to32(as_int); } // take care of nans: #define kh_float64_hash_equal(a, b) ((a) == (b) || ((b) != (b) && (a) != (a))) // 32bit typedef float float32_t; typedef float32_t khfloat32_t; CYKHASH_INLINE uint32_t kh_float32_hash_func(float32_t val){ if(val==0.0){ return ZERO_HASH; } if(val!=val){ return NAN_HASH; } int32_t as_int = f32_to_ui32(val); return murmur2_32to32(as_int); } // take care of nans: #define kh_float32_hash_equal(a, b) ((a) == (b) || ((b) != (b) && (a) != (a))) #endif """ pass cdef extern from *: """ #ifndef CYKHASH_PYOBJECT_HASH_FUNS_PXI #define CYKHASH_PYOBJECT_HASH_FUNS_PXI //khash has nothing predefined for Pyobject #include typedef PyObject* pyobject_t; typedef pyobject_t khpyobject_t; CYKHASH_INLINE int floatobject_cmp(PyFloatObject* a, PyFloatObject* b){ return ( Py_IS_NAN(PyFloat_AS_DOUBLE(a)) && Py_IS_NAN(PyFloat_AS_DOUBLE(b)) ) || ( PyFloat_AS_DOUBLE(a) == PyFloat_AS_DOUBLE(b) ); } CYKHASH_INLINE int complexobject_cmp(PyComplexObject* a, PyComplexObject* b){ return ( Py_IS_NAN(a->cval.real) && Py_IS_NAN(b->cval.real) && Py_IS_NAN(a->cval.imag) && Py_IS_NAN(b->cval.imag) ) || ( Py_IS_NAN(a->cval.real) && Py_IS_NAN(b->cval.real) && a->cval.imag == b->cval.imag ) || ( a->cval.real == b->cval.real && Py_IS_NAN(a->cval.imag) && Py_IS_NAN(b->cval.imag) ) || ( a->cval.real == b->cval.real && a->cval.imag == b->cval.imag ); } CYKHASH_INLINE int pyobject_cmp(PyObject* a, PyObject* b); CYKHASH_INLINE int tupleobject_cmp(PyTupleObject* a, PyTupleObject* b){ Py_ssize_t i; if (Py_SIZE(a) != Py_SIZE(b)) { return 0; } for (i = 0; i < Py_SIZE(a); ++i) { if (!pyobject_cmp(PyTuple_GET_ITEM(a, i), PyTuple_GET_ITEM(b, i))) { return 0; } } return 1; } CYKHASH_INLINE int pyobject_cmp(PyObject* a, PyObject* b) { if (a == b) { return 1; } if (Py_TYPE(a) == Py_TYPE(b)) { // special handling for some built-in types which could have NaNs: if (PyFloat_CheckExact(a)) { return floatobject_cmp((PyFloatObject*)a, (PyFloatObject*)b); } if (PyComplex_CheckExact(a)) { return complexobject_cmp((PyComplexObject*)a, (PyComplexObject*)b); } if (PyTuple_CheckExact(a)) { return tupleobject_cmp((PyTupleObject*)a, (PyTupleObject*)b); } // frozenset isn't yet supported } int result = PyObject_RichCompareBool(a, b, Py_EQ); if (result < 0) { PyErr_Clear(); return 0; } return result; } ///hashes: CYKHASH_INLINE Py_hash_t _Cykhash_HashDouble(double val) { //Since Python3.10, nan is no longer has hash 0 if (Py_IS_NAN(val)) { return 0; } #if PY_VERSION_HEX < 0x030A0000 return _Py_HashDouble(val); #else return _Py_HashDouble(NULL, val); #endif } CYKHASH_INLINE Py_hash_t floatobject_hash(PyFloatObject* key) { return _Cykhash_HashDouble(PyFloat_AS_DOUBLE(key)); } // replaces _Py_HashDouble with _Cykhash_HashDouble #define _CykhashHASH_IMAG 1000003UL CYKHASH_INLINE Py_hash_t complexobject_hash(PyComplexObject* key) { Py_uhash_t realhash = (Py_uhash_t)_Cykhash_HashDouble(key->cval.real); Py_uhash_t imaghash = (Py_uhash_t)_Cykhash_HashDouble(key->cval.imag); if (realhash == (Py_uhash_t)-1 || imaghash == (Py_uhash_t)-1) { return -1; } Py_uhash_t combined = realhash + _CykhashHASH_IMAG * imaghash; if (combined == (Py_uhash_t)-1) { return -2; } return (Py_hash_t)combined; } CYKHASH_INLINE uint32_t pyobject_hash(PyObject* key); //we could use any hashing algorithm, this is the original CPython's for tuples #if SIZEOF_PY_UHASH_T > 4 #define _CykhashHASH_XXPRIME_1 ((Py_uhash_t)11400714785074694791ULL) #define _CykhashHASH_XXPRIME_2 ((Py_uhash_t)14029467366897019727ULL) #define _CykhashHASH_XXPRIME_5 ((Py_uhash_t)2870177450012600261ULL) #define _CykhashHASH_XXROTATE(x) ((x << 31) | (x >> 33)) /* Rotate left 31 bits */ #else #define _CykhashHASH_XXPRIME_1 ((Py_uhash_t)2654435761UL) #define _CykhashHASH_XXPRIME_2 ((Py_uhash_t)2246822519UL) #define _CykhashHASH_XXPRIME_5 ((Py_uhash_t)374761393UL) #define _CykhashHASH_XXROTATE(x) ((x << 13) | (x >> 19)) /* Rotate left 13 bits */ #endif CYKHASH_INLINE Py_hash_t tupleobject_hash(PyTupleObject* key) { Py_ssize_t i, len = Py_SIZE(key); PyObject **item = key->ob_item; Py_uhash_t acc = _CykhashHASH_XXPRIME_5; for (i = 0; i < len; i++) { Py_uhash_t lane = pyobject_hash(item[i]); if (lane == (Py_uhash_t)-1) { return -1; } acc += lane * _CykhashHASH_XXPRIME_2; acc = _CykhashHASH_XXROTATE(acc); acc *= _CykhashHASH_XXPRIME_1; } /* Add input length, mangled to keep the historical value of hash(()). */ acc += len ^ (_CykhashHASH_XXPRIME_5 ^ 3527539UL); if (acc == (Py_uhash_t)-1) { return 1546275796; } return acc; } CYKHASH_INLINE uint32_t pyobject_hash(PyObject* key) { Py_hash_t hash; // For PyObject_Hash holds: // hash(0.0) == 0 == hash(-0.0) // yet for different nan-objects different hash-values // are possible if (PyFloat_CheckExact(key)) { // we cannot use kh_float64_hash_func // becase float(k) == k holds for any int-object k // and kh_float64_hash_func doesn't respect it hash = floatobject_hash((PyFloatObject*)key); } else if (PyComplex_CheckExact(key)) { // we cannot use kh_complex128_hash_func // becase complex(k,0) == k holds for any int-object k // and kh_complex128_hash_func doesn't respect it hash = complexobject_hash((PyComplexObject*)key); } else if (PyTuple_CheckExact(key)) { hash = tupleobject_hash((PyTupleObject*)key); } else { hash = PyObject_Hash(key); } if (hash == -1) { PyErr_Clear(); return 0; } #if SIZEOF_PY_HASH_T == 4 // it is already 32bit value return (uint32_t)hash; #else // for 64bit builds, // we need information of the upper 32bits as well // uints avoid undefined behavior of signed ints return kh_int64_hash_func((uint64_t) hash); #endif } #endif """ pass cdef extern from *: """ #ifndef CYKHASH_DEFINE_HASH_FUNS_PXI #define CYKHASH_DEFINE_HASH_FUNS_PXI // used hash-functions #define cykh_int32_hash_func murmur2_32to32 #define cykh_int64_hash_func murmur2_64to32 #define cykh_float32_hash_func kh_float32_hash_func #define cykh_float64_hash_func kh_float64_hash_func #define cykh_pyobject_hash_func pyobject_hash // used equality-functions #define cykh_int32_hash_equal kh_int32_hash_equal #define cykh_int64_hash_equal kh_int64_hash_equal #define cykh_float32_hash_equal kh_float32_hash_equal #define cykh_float64_hash_equal kh_float64_hash_equal #define cykh_pyobject_hash_equal pyobject_cmp #endif """ pass cykhash-2.0.0/src/cykhash/khash.pxi000066400000000000000000000516001414255425400172140ustar00rootroot00000000000000 #just use verbatim instead of h-file, so there is no need for setting include path for the compiler later on cdef extern from *: """ /* The MIT License Copyright (c) 2008, 2009, 2011 by Attractive Chaos Permission is hereby granted, CYKHASH_FREE of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* An example: #include "khash.h" KHASH_MAP_INIT_INT(32, char) int main() { int ret, is_missing; khiter_t k; khash_t(32) *h = kh_init(32); k = kh_put(32, h, 5, &ret); kh_value(h, k) = 10; k = kh_get(32, h, 10); is_missing = (k == kh_end(h)); k = kh_get(32, h, 5); kh_del(32, h, k); for (k = kh_begin(h); k != kh_end(h); ++k) if (kh_exist(h, k)) kh_value(h, k) = 1; kh_destroy(32, h); return 0; } */ /* 2013-05-02 (0.2.8): * Use quadratic probing. When the capacity is power of 2, stepping function i*(i+1)/2 guarantees to traverse each bucket. It is better than double hashing on cache performance and is more robust than linear probing. In theory, double hashing should be more robust than quadratic probing. However, my implementation is probably not for large hash tables, because the second hash function is closely tied to the first hash function, which reduce the effectiveness of double hashing. Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php 2011-12-29 (0.2.7): * Minor code clean up; no actual effect. 2011-09-16 (0.2.6): * The capacity is a power of 2. This seems to dramatically improve the speed for simple keys. Thank Zilong Tan for the suggestion. Reference: - http://code.google.com/p/ulib/ - http://nothings.org/computer/judy/ * Allow to optionally use linear probing which usually has better performance for random input. Double hashing is still the default as it is more robust to certain non-random input. * Added Wang's integer hash function (not used by default). This hash function is more robust to certain non-random input. 2011-02-14 (0.2.5): * Allow to declare global functions. 2009-09-26 (0.2.4): * Improve portability 2008-09-19 (0.2.3): * Corrected the example * Improved interfaces 2008-09-11 (0.2.2): * Improved speed a little in kh_put() 2008-09-10 (0.2.1): * Added kh_clear() * Fixed a compiling error 2008-09-02 (0.2.0): * Changed to token concatenation which increases flexibility. 2008-08-31 (0.1.2): * Fixed a bug in kh_get(), which has not been tested previously. 2008-08-31 (0.1.1): * Added destructor */ #ifndef __AC_KHASH_H #define __AC_KHASH_H /*! @header Generic hash table library. */ #define AC_VERSION_KHASH_H "0.2.8" #include #include #include /* compiler specific configuration */ #if UINT_MAX == 0xffffffffu typedef unsigned int khuint32_t; typedef int khint32_t; #elif ULONG_MAX == 0xffffffffu typedef unsigned long khuint32_t; typedef long khint32_t; #endif #if ULONG_MAX == ULLONG_MAX typedef unsigned long khuint64_t; typedef long khint64_t; #else typedef unsigned long long khuint64_t; typedef long long khint64_t; #endif #ifndef kh_inline #ifdef _MSC_VER #define kh_inline __inline #else #define kh_inline inline #endif #endif /* kh_inline */ #ifndef klib_unused #if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) #define klib_unused __attribute__ ((__unused__)) #else #define klib_unused #endif #endif /* klib_unused */ typedef khuint32_t khint_t; typedef khint_t khiter_t; #define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) #define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) #define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) #define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) #define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) #define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) #define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) #define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) #ifndef kroundup32 #define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) #endif #ifndef kCYKHASH_CALLOC #define kCYKHASH_CALLOC(N,Z) CYKHASH_CALLOC(N,Z) #endif #ifndef kCYKHASH_MALLOC #define kCYKHASH_MALLOC(Z) CYKHASH_MALLOC(Z) #endif #ifndef kCYKHASH_REALLOC #define kCYKHASH_REALLOC(P,Z) CYKHASH_REALLOC(P,Z) #endif #ifndef kCYKHASH_FREE #define kCYKHASH_FREE(P) CYKHASH_FREE(P) #endif static const double __ac_HASH_UPPER = 0.77; #define __KHASH_TYPE(name, khkey_t, khval_t) \ typedef struct kh_##name##_s { \ khint_t n_buckets, size, n_occupied, upper_bound; \ khuint32_t *flags; \ khkey_t *keys; \ khval_t *vals; \ } kh_##name##_t; #define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ extern kh_##name##_t *kh_init_##name(void); \ extern void kh_destroy_##name(kh_##name##_t *h); \ extern void kh_clear_##name(kh_##name##_t *h); \ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ extern void kh_del_##name(kh_##name##_t *h, khint_t x); #define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ SCOPE kh_##name##_t *kh_init_##name(void) { \ return (kh_##name##_t*)kCYKHASH_CALLOC(1, sizeof(kh_##name##_t)); \ } \ SCOPE void kh_destroy_##name(kh_##name##_t *h) \ { \ if (h) { \ kCYKHASH_FREE((void *)h->keys); kCYKHASH_FREE(h->flags); \ kCYKHASH_FREE((void *)h->vals); \ kCYKHASH_FREE(h); \ } \ } \ SCOPE void kh_clear_##name(kh_##name##_t *h) \ { \ if (h && h->flags) { \ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khuint32_t)); \ h->size = h->n_occupied = 0; \ } \ } \ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ { \ if (h->n_buckets) { \ khint_t k, i, last, mask, step = 0; \ mask = h->n_buckets - 1; \ k = __hash_func(key); i = k & mask; \ last = i; \ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ i = (i + (++step)) & mask; \ if (i == last) return h->n_buckets; \ } \ return __ac_iseither(h->flags, i)? h->n_buckets : i; \ } else return 0; \ } \ SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ khuint32_t *new_flags = 0; \ khint_t j = 1; \ { \ kroundup32(new_n_buckets); \ if (new_n_buckets < 4) new_n_buckets = 4; \ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ else { /* hash table size to be changed (shrink or expand); rehash */ \ new_flags = (khuint32_t*)kCYKHASH_MALLOC(__ac_fsize(new_n_buckets) * sizeof(khuint32_t)); \ if (!new_flags) return -1; \ memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khuint32_t)); \ if (h->n_buckets < new_n_buckets) { /* expand */ \ khkey_t *new_keys = (khkey_t*)kCYKHASH_REALLOC((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ if (!new_keys) { kCYKHASH_FREE(new_flags); return -1; } \ h->keys = new_keys; \ if (kh_is_map) { \ khval_t *new_vals = (khval_t*)kCYKHASH_REALLOC((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ if (!new_vals) { kCYKHASH_FREE(new_flags); return -1; } \ h->vals = new_vals; \ } \ } /* otherwise shrink */ \ } \ } \ if (j) { /* rehashing is needed */ \ for (j = 0; j != h->n_buckets; ++j) { \ if (__ac_iseither(h->flags, j) == 0) { \ khkey_t key = h->keys[j]; \ khval_t val; \ khint_t new_mask; \ new_mask = new_n_buckets - 1; \ if (kh_is_map) val = h->vals[j]; \ __ac_set_isdel_true(h->flags, j); \ while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ khint_t k, i, step = 0; \ k = __hash_func(key); \ i = k & new_mask; \ while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ __ac_set_isempty_false(new_flags, i); \ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ } else { /* write the element and jump out of the loop */ \ h->keys[i] = key; \ if (kh_is_map) h->vals[i] = val; \ break; \ } \ } \ } \ } \ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ h->keys = (khkey_t*)kCYKHASH_REALLOC((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ if (kh_is_map) h->vals = (khval_t*)kCYKHASH_REALLOC((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ } \ kCYKHASH_FREE(h->flags); /* CYKHASH_FREE the working space */ \ h->flags = new_flags; \ h->n_buckets = new_n_buckets; \ h->n_occupied = h->size; \ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ } \ return 0; \ } \ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ { \ khint_t x; \ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ if (h->n_buckets > (h->size<<1)) { \ if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ *ret = -1; return h->n_buckets; \ } \ } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ *ret = -1; return h->n_buckets; \ } \ } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ { \ khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ else { \ last = i; \ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ if (__ac_isdel(h->flags, i)) site = i; \ i = (i + (++step)) & mask; \ if (i == last) { x = site; break; } \ } \ if (x == h->n_buckets) { \ if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ else x = i; \ } \ } \ } \ if (__ac_isempty(h->flags, x)) { /* not present at all */ \ h->keys[x] = key; \ __ac_set_isboth_false(h->flags, x); \ ++h->size; ++h->n_occupied; \ *ret = 1; \ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ h->keys[x] = key; \ __ac_set_isboth_false(h->flags, x); \ ++h->size; \ *ret = 2; \ } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ return x; \ } \ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ { \ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ __ac_set_isdel_true(h->flags, x); \ --h->size; \ } \ } #define KHASH_DECLARE(name, khkey_t, khval_t) \ __KHASH_TYPE(name, khkey_t, khval_t) \ __KHASH_PROTOTYPES(name, khkey_t, khval_t) #define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ __KHASH_TYPE(name, khkey_t, khval_t) \ __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) #define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) /* Other convenient macros... */ /*! @abstract Type of the hash table. @param name Name of the hash table [symbol] */ #define khash_t(name) kh_##name##_t /*! @function @abstract Initiate a hash table. @param name Name of the hash table [symbol] @return Pointer to the hash table [khash_t(name)*] */ #define kh_init(name) kh_init_##name() /*! @function @abstract Destroy a hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] */ #define kh_destroy(name, h) kh_destroy_##name(h) /*! @function @abstract Reset a hash table without deallocating memory. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] */ #define kh_clear(name, h) kh_clear_##name(h) /*! @function @abstract Resize a hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param s New size [khint_t] */ #define kh_resize(name, h, s) kh_resize_##name(h, s) /*! @function @abstract Insert a key to the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Key [type of keys] @param r Extra return code: -1 if the operation failed; 0 if the key is present in the hash table; 1 if the bucket is empty (never used); 2 if the element in the bucket has been deleted [int*] @return Iterator to the inserted element [khint_t] */ #define kh_put(name, h, k, r) kh_put_##name(h, k, r) /*! @function @abstract Retrieve a key from the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Key [type of keys] @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] */ #define kh_get(name, h, k) kh_get_##name(h, k) /*! @function @abstract Remove a key from the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Iterator to the element to be deleted [khint_t] */ #define kh_del(name, h, k) kh_del_##name(h, k) /*! @function @abstract Test whether a bucket contains data. @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return 1 if containing data; 0 otherwise [int] */ #define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) /*! @function @abstract Get key given an iterator @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return Key [type of keys] */ #define kh_key(h, x) ((h)->keys[x]) /*! @function @abstract Get value given an iterator @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return Value [type of values] @discussion For hash sets, calling this results in segfault. */ #define kh_val(h, x) ((h)->vals[x]) /*! @function @abstract Alias of kh_val() */ #define kh_value(h, x) ((h)->vals[x]) /*! @function @abstract Get the start iterator @param h Pointer to the hash table [khash_t(name)*] @return The start iterator [khint_t] */ #define kh_begin(h) (khint_t)(0) /*! @function @abstract Get the end iterator @param h Pointer to the hash table [khash_t(name)*] @return The end iterator [khint_t] */ #define kh_end(h) ((h)->n_buckets) /*! @function @abstract Get the number of elements in the hash table @param h Pointer to the hash table [khash_t(name)*] @return Number of elements in the hash table [khint_t] */ #define kh_size(h) ((h)->size) /*! @function @abstract Get the number of buckets in the hash table @param h Pointer to the hash table [khash_t(name)*] @return Number of buckets in the hash table [khint_t] */ #define kh_n_buckets(h) ((h)->n_buckets) /*! @function @abstract Iterate over the entries in the hash table @param h Pointer to the hash table [khash_t(name)*] @param kvar Variable to which key will be assigned @param vvar Variable to which value will be assigned @param code Block of code to execute */ #define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ if (!kh_exist(h,__i)) continue; \ (kvar) = kh_key(h,__i); \ (vvar) = kh_val(h,__i); \ code; \ } } /*! @function @abstract Iterate over the values in the hash table @param h Pointer to the hash table [khash_t(name)*] @param vvar Variable to which value will be assigned @param code Block of code to execute */ #define kh_foreach_value(h, vvar, code) { khint_t __i; \ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ if (!kh_exist(h,__i)) continue; \ (vvar) = kh_val(h,__i); \ code; \ } } #endif /* __AC_KHASH_H */ """ ctypedef uint32_t khint_t cdef extern from *: """ khint_t element_n_to_bucket_n(khint_t element_n){ khint_t candidate = element_n; kroundup32(candidate); khint_t upper_bound = (khint_t)(candidate * __ac_HASH_UPPER + 0.5); return (upper_bound < element_n) ? 2*candidate : candidate; } khint_t element_n_from_size_hint(khint_t element_n, double size_hint){ if(size_hint>0.0){ return (khint_t)(element_n * size_hint); } return element_n; } khint_t bucket_n_from_size_hint(khint_t element_n, double size_hint){ if(size_hint>0.0){ return (khint_t)(element_n * size_hint); } return element_n_to_bucket_n(element_n); } """ khint_t element_n_to_bucket_n(khint_t element_n) khint_t element_n_from_size_hint(khint_t element_n, double size_hint) khint_t bucket_n_from_size_hint(khint_t element_n, double size_hint) cykhash-2.0.0/src/cykhash/khashmaps.pxd000066400000000000000000000006271414255425400200730ustar00rootroot00000000000000from libc.stdint cimport uint64_t, uint32_t, int64_t, int32_t from cpython.object cimport PyObject from .floatdef cimport float64_t, float32_t ### Common definitions: ctypedef PyObject* pyobject_t include "common.pxi" include "memory.pxi" include "khash.pxi" include "murmurhash.pxi" #utilities for int<->float include "hash_functions.pxi" # different implementation include "maps/map_header.pxi" cykhash-2.0.0/src/cykhash/khashmaps.pyx000066400000000000000000000021411414255425400201110ustar00rootroot00000000000000 # different implementations: include "maps/map_impl.pxi" # backward compartibility: def Float64to64Map(*, number_of_elements_hint=None, for_int=True): if for_int: return Float64toInt64Map(number_of_elements_hint=number_of_elements_hint) else: return Float64toFloat64Map(number_of_elements_hint=number_of_elements_hint) def Int64to64Map(*, number_of_elements_hint=None, for_int=True): if for_int: return Int64toInt64Map(number_of_elements_hint=number_of_elements_hint) else: return Int64toFloat64Map(number_of_elements_hint=number_of_elements_hint) def Float32to32Map(*, number_of_elements_hint=None, for_int=True): if for_int: return Float32toInt32Map(number_of_elements_hint=number_of_elements_hint) else: return Float32toFloat32Map(number_of_elements_hint=number_of_elements_hint) def Int32to32Map(*, number_of_elements_hint=None, for_int=True): if for_int: return Int32toInt32Map(number_of_elements_hint=number_of_elements_hint) else: return Int32toFloat32Map(number_of_elements_hint=number_of_elements_hint) cykhash-2.0.0/src/cykhash/khashsets.pxd000066400000000000000000000006321414255425400201050ustar00rootroot00000000000000from libc.stdint cimport uint64_t, uint32_t, int64_t, int32_t from cpython.object cimport PyObject from .floatdef cimport float64_t, float32_t ### Common definitions: ctypedef PyObject* pyobject_t include "common.pxi" include "memory.pxi" include "khash.pxi" include "murmurhash.pxi" #utilities for int<->float include "hash_functions.pxi" # different implementations include "sets/set_header.pxi" cykhash-2.0.0/src/cykhash/khashsets.pyx000066400000000000000000000000731414255425400201310ustar00rootroot00000000000000 # different implementations: include "sets/set_impl.pxi" cykhash-2.0.0/src/cykhash/maps/000077500000000000000000000000001414255425400163325ustar00rootroot00000000000000cykhash-2.0.0/src/cykhash/maps/map_header.pxi.in000066400000000000000000000122061414255425400215470ustar00rootroot00000000000000""" Template for maps WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in """ include "map_init.pxi" {{py: # map_name, name, key_type, val_type map_types = [('Int64toInt64', 'int64toint64', 'int64_t', 'int64_t'), ('Int64toFloat64', 'int64tofloat64', 'int64_t', 'float64_t'), ('Float64toInt64', 'float64toint64', 'float64_t', 'int64_t'), ('Float64toFloat64', 'float64tofloat64', 'float64_t', 'float64_t'), ('Int32toInt32', 'int32toint32', 'int32_t', 'int32_t'), ('Int32toFloat32', 'int32tofloat32', 'int32_t', 'float32_t'), ('Float32toInt32', 'float32toint32', 'float32_t', 'int32_t'), ('Float32toFloat32', 'float32tofloat32', 'float32_t', 'float32_t'), ] }} {{for map_name, name, key_type, val_type in map_types}} cdef extern from *: ctypedef struct kh_{{name}}map_t: khint_t n_buckets, size, n_occupied, upper_bound uint32_t *flags {{key_type}} *keys {{val_type}} *vals kh_{{name}}map_t* kh_init_{{name}}map() nogil void kh_destroy_{{name}}map(kh_{{name}}map_t*) nogil void kh_clear_{{name}}map(kh_{{name}}map_t*) nogil khint_t kh_get_{{name}}map(kh_{{name}}map_t*, {{key_type}}) nogil void kh_resize_{{name}}map(kh_{{name}}map_t*, khint_t) nogil khint_t kh_put_{{name}}map(kh_{{name}}map_t*, {{key_type}}, int* result) nogil void kh_del_{{name}}map(kh_{{name}}map_t*, khint_t) nogil #specializing "kh_exist"-macro bint kh_exist_{{name}}map "kh_exist" (kh_{{name}}map_t*, khint_t) nogil cdef class {{map_name}}Map: cdef kh_{{name}}map_t *table cdef bint for_int cdef bint contains(self, {{key_type}} key) except * cdef {{map_name}}MapIterator get_iter(self, int view_type) cdef khint_t size(self) cpdef void cput(self, {{key_type}} key, {{val_type}} value) except * cpdef {{val_type}} cget(self, {{key_type}} key) except * cpdef void discard(self, {{key_type}} key) except * cdef struct {{name}}_key_val_pair: {{key_type}} key {{val_type}} val cdef class {{map_name}}MapIterator: cdef khint_t it cdef int view_type cdef {{map_name}}Map parent cdef bint has_next(self) except * cdef {{name}}_key_val_pair next(self) except * cdef void __move(self) except * cdef class {{map_name}}MapView: cdef {{map_name}}Map parent cdef int view_type cdef {{map_name}}MapIterator get_iter(self) cpdef {{map_name}}Map {{map_name}}Map_from_buffers({{key_type}}[:] keys, {{val_type}}[:] vals, double size_hint=*) cpdef size_t {{map_name}}Map_to({{map_name}}Map map, {{key_type}}[:] keys, {{val_type}}[:] vals, bint stop_at_unknown=*, {{val_type}} default_value=*) except * # other help functions: cpdef void swap_{{name}}map({{map_name}}Map a, {{map_name}}Map b) except * cpdef {{map_name}}Map copy_{{name}}map({{map_name}}Map s) cpdef bint are_equal_{{name}}map({{map_name}}Map a, {{map_name}}Map b) except * cpdef void update_{{name}}map({{map_name}}Map a, {{map_name}}Map b) except * {{endfor}} ##TODO: unify with others cdef extern from *: ctypedef struct kh_pyobjectmap_t: khint_t n_buckets, size, n_occupied, upper_bound uint32_t *flags pyobject_t *keys pyobject_t *vals kh_pyobjectmap_t* kh_init_pyobjectmap() nogil void kh_destroy_pyobjectmap(kh_pyobjectmap_t*) nogil void kh_clear_pyobjectmap(kh_pyobjectmap_t*) nogil khint_t kh_get_pyobjectmap(kh_pyobjectmap_t*, pyobject_t) nogil void kh_resize_pyobjectmap(kh_pyobjectmap_t*, khint_t) nogil khint_t kh_put_pyobjectmap(kh_pyobjectmap_t*, pyobject_t, int* result) nogil void kh_del_pyobjectmap(kh_pyobjectmap_t*, khint_t) nogil #specializing "kh_exist"-macro bint kh_exist_pyobjectmap "kh_exist" (kh_pyobjectmap_t*, khint_t) nogil cdef class PyObjectMap: cdef kh_pyobjectmap_t *table cdef bint contains(self, pyobject_t key) except * cdef PyObjectMapIterator get_iter(self, int view_type) cdef khint_t size(self) cpdef void cput(self, object key, object value) except * cpdef object cget(self, object key) cpdef void discard(self, object key) except * cdef struct pyobject_key_val_pair: pyobject_t key pyobject_t val cdef class PyObjectMapIterator: cdef khint_t it cdef int view_type cdef PyObjectMap parent cdef bint has_next(self) except * cdef pyobject_key_val_pair next(self) except * cdef void __move(self) except * cdef class PyObjectMapView: cdef PyObjectMap parent cdef int view_type cdef PyObjectMapIterator get_iter(self) cpdef PyObjectMap PyObjectMap_from_buffers(object[:] keys, object[:] vals, double size_hint=*) cpdef size_t PyObjectMap_to(PyObjectMap map, object[:] keys, object[:] vals, bint stop_at_unknown=*, object default_value=*) except * # other help functions: cpdef void swap_pyobjectmap(PyObjectMap a, PyObjectMap b) except * cpdef PyObjectMap copy_pyobjectmap(PyObjectMap s) cpdef bint are_equal_pyobjectmap(PyObjectMap a, PyObjectMap b) except * cpdef void update_pyobjectmap(PyObjectMap a, PyObjectMap b) except * cykhash-2.0.0/src/cykhash/maps/map_impl.pxi.in000066400000000000000000000327771414255425400212770ustar00rootroot00000000000000""" Template for maps WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in """ from cpython.ref cimport Py_INCREF,Py_DECREF {{py: # map_name, name, key_type, val_type map_types = [('Int64toInt64', 'int64toint64', 'int64_t', 'int64_t'), ('Int64toFloat64', 'int64tofloat64', 'int64_t', 'float64_t'), ('Float64toInt64', 'float64toint64', 'float64_t', 'int64_t'), ('Float64toFloat64', 'float64tofloat64', 'float64_t', 'float64_t'), ('Int32toInt32', 'int32toint32', 'int32_t', 'int32_t'), ('Int32toFloat32', 'int32tofloat32', 'int32_t', 'float32_t'), ('Float32toInt32', 'float32toint32', 'float32_t', 'int32_t'), ('Float32toFloat32', 'float32tofloat32', 'float32_t', 'float32_t'), ('PyObject', 'pyobject', 'object', 'object'), ] }} {{for map_name, name, key_type, val_type in map_types}} cdef class {{map_name}}Map: {{if map_name != 'PyObject'}} @classmethod def fromkeys(cls, iterable, value): return {{map_name}}Map(((key, value) for key in iterable)) def __cinit__(self, iterable=None, *, number_of_elements_hint=None): """ number_of_elements_hint - number of elements without the need of reallocation. """ self.table = kh_init_{{name}}map() if number_of_elements_hint is not None: kh_resize_{{name}}map(self.table, element_n_to_bucket_n(number_of_elements_hint)) cdef {{key_type}} key cdef {{val_type}} val if iterable is not None: for key, val in iterable: self.cput(key, val) def __dealloc__(self): if self.table is not NULL: kh_destroy_{{name}}map(self.table) self.table = NULL cpdef void discard(self, {{key_type}} key) except *: cdef khint_t k k = kh_get_{{name}}map(self.table, key) if k != self.table.n_buckets: kh_del_{{name}}map(self.table, k) cdef bint contains(self, {{key_type}} key) except *: cdef khint_t k k = kh_get_{{name}}map(self.table, key) return k != self.table.n_buckets def __contains__(self, {{key_type}} key): return self.contains(key) {{else}} @classmethod def fromkeys(cls, iterable, value): return {{map_name}}Map((key, value) for key in iterable) def __cinit__(self, iterable=None, *, number_of_elements_hint=None): """ number_of_elements_hint - number of elements without the need of reallocation. """ self.table = kh_init_pyobjectmap() if number_of_elements_hint is not None: kh_resize_pyobjectmap(self.table, element_n_to_bucket_n(number_of_elements_hint)) if iterable is not None: for key, val in iterable: self.cput(key, val) cpdef void discard(self, object key) except *: cdef khint_t k k = kh_get_pyobjectmap(self.table, key) if k != self.table.n_buckets: Py_DECREF((self.table.keys[k])) Py_DECREF((self.table.vals[k])) kh_del_pyobjectmap(self.table, k) def __dealloc__(self): cdef Py_ssize_t i if self.table is not NULL: for i in range(self.table.size): if kh_exist_pyobjectmap(self.table, i): Py_DECREF((self.table.keys[i])) Py_DECREF((self.table.vals[i])) kh_destroy_pyobjectmap(self.table) self.table = NULL cdef bint contains(self, pyobject_t key) except *: cdef khint_t k k = kh_get_pyobjectmap(self.table, key) return k != self.table.n_buckets def __contains__(self, object key): return self.contains(key) {{endif}} def __len__(self): return self.size() cdef khint_t size(self): return self.table.size {{if map_name != 'PyObject'}} cpdef void cput(self, {{key_type}} key, {{val_type}} val) except *: cdef: khint_t k int ret = 0 k = kh_put_{{name}}map(self.table, key, &ret) self.table.keys[k] = key self.table.vals[k] = val def __setitem__(self, key, val): self.cput(key, val) cpdef {{val_type}} cget(self, {{key_type}} key) except *: k = kh_get_{{name}}map(self.table, key) if k != self.table.n_buckets: return self.table.vals[k] else: raise KeyError(key) def __getitem__(self, key): return self.cget(key) {{else}} cpdef void cput(self, object key, object val) except *: cdef: khint_t k int ret = 0 k = kh_put_pyobjectmap(self.table, key, &ret) if not ret: Py_DECREF((self.table.vals[k])) else: Py_INCREF(key) Py_INCREF(val) self.table.vals[k] = val def __setitem__(self, key, val): self.cput(key, val) cpdef object cget(self, object key): k = kh_get_pyobjectmap(self.table, key) if k != self.table.n_buckets: return self.table.vals[k] else: raise KeyError(key) def __getitem__(self, key): return self.cget(key) {{endif}} cdef {{map_name}}MapIterator get_iter(self, int view_type): return {{map_name}}MapIterator(self, view_type) def clear(self): cdef {{map_name}}Map tmp={{map_name}}Map() swap_{{name}}map(self, tmp) def copy(self): return copy_{{name}}map(self) def update(self, other): if isinstance(other, {{map_name}}Map): update_{{name}}map(self, other) return for key,val in other: self[key]=val def setdefault(self, key, default): try: return self[key] except KeyError: self[key]=default return default def get(self, *args, **kwargs): if len(args)==0: raise TypeError("get() expected at least 1 arguments, got 0") if len(args)>2: raise TypeError("get() expected at most 2 arguments, got {0}".format(len(args))) if kwargs: raise TypeError("get() takes no keyword arguments") key = args[0] try: return self[key] except KeyError: if len(args)==1: return None return args[1] def pop(self, *args, **kwargs): if len(args)==0: raise TypeError("pop() expected at least 1 arguments, got 0") if len(args)>2: raise TypeError("pop() expected at most 2 arguments, got {0}".format(len(args))) if kwargs: raise TypeError("pop() takes no keyword arguments") key = args[0] try: val = self[key] except KeyError as e: if len(args)==1: raise e from None return args[1] del self[key] return val def popitem(self): if self.size()== 0: raise KeyError("popitem(): dictionary is empty") key = next(iter(self)) val = self.pop(key) return (key, val) def keys(self): return {{map_name}}MapView(self, 0) def values(self): return {{map_name}}MapView(self, 1) def items(self): return {{map_name}}MapView(self, 2) def __iter__(self): return iter(self.keys()) def __delitem__(self, key): cdef size_t old=self.size() self.discard(key) if old==self.size(): raise KeyError(key) def __eq__(self, other): return are_equal_{{name}}map(self,other) ### Iterator: cdef class {{map_name}}MapIterator: cdef void __move(self) except *: while self.itpair.key if self.view_type == 1: # vals return pair.val else: # items return (pair.key, pair.val) {{else}} if self.view_type == 0: # keys return pair.key if self.view_type == 1: # vals return pair.val else: # items return (pair.key, pair.val) {{endif}} else: raise StopIteration cdef class {{map_name}}MapView: cdef {{map_name}}MapIterator get_iter(self): return {{map_name}}MapIterator(self.parent, self.view_type) def __cinit__(self, {{map_name}}Map parent, view_type): self.parent = parent self.view_type = view_type def __iter__(self): return self.get_iter() def __len__(self): return self.parent.size() def __contains__(self, x): for y in self: if x==y: return True return False ########################## Utils: {{if map_name != 'PyObject'}} cpdef {{map_name}}Map {{map_name}}Map_from_buffers({{key_type}}[:] keys, {{val_type}}[:] vals, double size_hint=0.0): {{else}} cpdef PyObjectMap PyObjectMap_from_buffers(object[:] keys, object[:] vals, double size_hint=0.0): {{endif}} cdef Py_ssize_t n = len(keys) cdef Py_ssize_t b = len(vals) if b < n: n = b cdef Py_ssize_t at_least_needed = element_n_from_size_hint(n, size_hint) res={{map_name}}Map(number_of_elements_hint=at_least_needed) cdef Py_ssize_t i for i in range(n): res.cput(keys[i], vals[i]) return res {{if val_type == 'object'}} cdef object DEFAULT_VALUE_{{name}} = None {{elif val_type.startswith('float')}} cdef {{val_type}} DEFAULT_VALUE_{{name}} = float("nan") {{else}} cdef {{val_type}} DEFAULT_VALUE_{{name}} = 0 {{endif}} {{if map_name != 'PyObject'}} cpdef size_t {{map_name}}Map_to({{map_name}}Map map, {{key_type}}[:] keys, {{val_type}}[:] vals, bint stop_at_unknown=True, {{val_type}} default_value=DEFAULT_VALUE_{{name}}) except *: {{else}} cpdef size_t PyObjectMap_to(PyObjectMap map, object[:] keys, object[:] vals, bint stop_at_unknown=True, object default_value=None) except *: {{endif}} """returns number of found keys""" if map is None: raise TypeError("'NoneType' is not a map") cdef size_t n = len(keys) if n != len(vals): raise ValueError("Different lengths of keys and vals arrays") cdef size_t i cdef khint_t k cdef size_t res = 0 for i in range(n): {{if map_name != 'PyObject'}} k = kh_get_{{name}}map(map.table, keys[i]) {{else}} k = kh_get_{{name}}map(map.table, keys[i]) {{endif}} if k != map.table.n_buckets: {{if map_name != 'PyObject'}} vals[i] = map.table.vals[k] {{else}} vals[i] = map.table.vals[k] {{endif}} res += 1 else: vals[i] = default_value if stop_at_unknown: return res return res cpdef void swap_{{name}}map({{map_name}}Map a, {{map_name}}Map b) except *: if a is None or b is None: raise TypeError("'NoneType' object is not iterable") cdef kh_{{name}}map_t *tmp=a.table a.table=b.table b.table=tmp cpdef {{map_name}}Map copy_{{name}}map({{map_name}}Map s): if s is None: return None cdef {{map_name}}Map result = {{map_name}}Map(number_of_elements_hint=s.size()) cdef {{map_name}}MapIterator it=s.get_iter(2) cdef {{name}}_key_val_pair p while it.has_next(): p = it.next() {{if map_name == 'PyObject'}} result.cput(p.key, p.val) {{else}} result.cput(p.key, p.val) {{endif}} return result cpdef bint are_equal_{{name}}map({{map_name}}Map a, {{map_name}}Map b) except *: if a is None or b is None: raise TypeError("'NoneType' object is not iterable") if a.size()!=b.size(): return False cdef {{map_name}}MapIterator it=a.get_iter(2) cdef {{name}}_key_val_pair p while it.has_next(): p = it.next() if not b.contains(p.key): return False return True cpdef void update_{{name}}map({{map_name}}Map a, {{map_name}}Map b) except *: if a is None or b is None: raise TypeError("'NoneType' object is not iterable") cdef {{map_name}}MapIterator it=b.get_iter(2) cdef {{name}}_key_val_pair p while it.has_next(): p = it.next() {{if map_name == 'PyObject'}} a.cput(p.key, p.val) {{else}} a.cput(p.key, p.val) {{endif}} {{endfor}} cykhash-2.0.0/src/cykhash/maps/map_init.pxi.in000066400000000000000000000035531414255425400212670ustar00rootroot00000000000000""" Template for maps WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in """ ################### INTS: {{py: # number of bits all_bits = ['64', '32'] }} {{for bits in all_bits}} cdef extern from *: """ // preprocessor creates needed struct-type and all function definitions #define CYKHASH_MAP_INIT_INT{{bits}}(name, khval_t) \ KHASH_INIT(name, int{{bits}}_t, khval_t, 1, cykh_float{{bits}}_hash_func, cykh_int{{bits}}_hash_equal) CYKHASH_MAP_INIT_INT{{bits}}(int{{bits}}toint{{bits}}map, int{{bits}}_t) CYKHASH_MAP_INIT_INT{{bits}}(int{{bits}}tofloat{{bits}}map, float{{bits}}_t) """ pass {{endfor}} ################ FLOATS: {{py: # number of bits all_bits = ['64', '32'] }} {{for bits in all_bits}} # see float_utils.pxi for definitions cdef extern from *: """ // preprocessor creates needed struct-type and all function definitions #define CYKHASH_MAP_INIT_FLOAT{{bits}}(name, khval_t) \ KHASH_INIT(name, khfloat{{bits}}_t, khval_t, 1, cykh_float{{bits}}_hash_func, cykh_float{{bits}}_hash_equal) CYKHASH_MAP_INIT_FLOAT{{bits}}(float{{bits}}toint{{bits}}map, int{{bits}}_t) CYKHASH_MAP_INIT_FLOAT{{bits}}(float{{bits}}tofloat{{bits}}map, float{{bits}}_t) """ pass {{endfor}} ################ OBJECT: cdef extern from *: """ // preprocessor creates needed struct-type and all function definitions // map with keys of type pyobject -> result pyobject #define CYKHASH_MAP_INIT_PYOBJECT(name, khval_t) \ KHASH_INIT(name, khpyobject_t, khval_t, 1, cykh_pyobject_hash_func, cykh_pyobject_hash_equal) //preprocessor creates needed struct-type and all function definitions //set with keys of type pyobject -> resulting typename: kh_pyobjectmap_t; CYKHASH_MAP_INIT_PYOBJECT(pyobjectmap, pyobject_t) """ pass cykhash-2.0.0/src/cykhash/memory.pxi000066400000000000000000000043211414255425400174240ustar00rootroot00000000000000 cdef extern from *: """ #ifndef CYKHASH_MEMORY_PXI #define CYKHASH_MEMORY_PXI #include // cykhash should report usage to tracemalloc #if PY_VERSION_HEX >= 0x03060000 #include #if PY_VERSION_HEX < 0x03070000 #define PyTraceMalloc_Track _PyTraceMalloc_Track #define PyTraceMalloc_Untrack _PyTraceMalloc_Untrack #endif #else #define PyTraceMalloc_Track(...) #define PyTraceMalloc_Untrack(...) #endif static const int CYKHASH_TRACE_DOMAIN = 414141; CYKHASH_INLINE void *cykhash_traced_malloc(size_t size){ void * ptr = malloc(size); if(ptr!=NULL){ PyTraceMalloc_Track(CYKHASH_TRACE_DOMAIN, (uintptr_t)ptr, size); } return ptr; } CYKHASH_INLINE void *cykhash_traced_calloc(size_t num, size_t size){ void * ptr = calloc(num, size); if(ptr!=NULL){ PyTraceMalloc_Track(CYKHASH_TRACE_DOMAIN, (uintptr_t)ptr, num*size); } return ptr; } CYKHASH_INLINE void *cykhash_traced_realloc(void* old_ptr, size_t size){ void * ptr = realloc(old_ptr, size); if(ptr!=NULL){ if(old_ptr != ptr){ PyTraceMalloc_Untrack(CYKHASH_TRACE_DOMAIN, (uintptr_t)old_ptr); } PyTraceMalloc_Track(CYKHASH_TRACE_DOMAIN, (uintptr_t)ptr, size); } return ptr; } CYKHASH_INLINE void cykhash_traced_free(void* ptr){ if(ptr!=NULL){ PyTraceMalloc_Untrack(CYKHASH_TRACE_DOMAIN, (uintptr_t)ptr); } free(ptr); } #define CYKHASH_MALLOC cykhash_traced_malloc #define CYKHASH_REALLOC cykhash_traced_realloc #define CYKHASH_CALLOC cykhash_traced_calloc #define CYKHASH_FREE cykhash_traced_free #endif """ const int CYKHASH_TRACE_DOMAIN void *cykhash_traced_malloc(size_t size) void *cykhash_traced_calloc(size_t num, size_t size) void *cykhash_traced_realloc(void* old_ptr, size_t size) void cykhash_traced_free(void* ptr) cykhash-2.0.0/src/cykhash/murmurhash.pxi000066400000000000000000000067701414255425400203210ustar00rootroot00000000000000 cdef extern from *: """ #ifndef CYKHASH_MURMURHASH_PXI #define CYKHASH_MURMURHASH_PXI #include // //specializations of https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp // const uint32_t SEED = 0xc70f6907UL; // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. const uint32_t M_32 = 0x5bd1e995; const int R_32 = 24; CYKHASH_INLINE uint32_t murmur2_32to32(uint32_t k){ // Initialize the hash to a 'random' value uint32_t h = SEED ^ 4; //handle 4 bytes: k *= M_32; k ^= k >> R_32; k *= M_32; h *= M_32; h ^= k; // Do a few final mixes of the hash to ensure the "last few // bytes" are well-incorporated. // TODO: really needed, we have no "last few bytes"? h ^= h >> 13; h *= M_32; h ^= h >> 15; return h; } #if INTPTR_MAX == INT64_MAX // 64-bit const uint64_t SEED_64 = 0xc70f6907b8107a18ULL; const uint64_t M_64= 0xc6a4a7935bd1e995ULL; const int R_64 = 47; CYKHASH_INLINE uint32_t murmur2_64to32(uint64_t k){ uint64_t h = SEED_64 ^ (8 * M_64); k *= M_64; k ^= k >> R_64; k *= M_64; h ^= k; h *= M_64; h ^= h >> R_64; h *= M_64; h ^= h >> R_64; // if hash h is good, we just can xor both halfs // (or take any 32 bit out of h) return (uint32_t)((h>>32)^h); } #elif INTPTR_MAX == INT32_MAX // 32-bit // uint64_t mult is slow for 32 bit, so falling back to murmur2_32to32 algorithm CYKHASH_INLINE uint32_t murmur2_32_32to32(uint32_t k1, uint32_t k2){ // Initialize the hash to a 'random' value uint32_t h = SEED ^ 4; //handle first 4 bytes: k1 *= M_32; k1 ^= k1 >> R_32; k1 *= M_32; h *= M_32; h ^= k1; //handle second 4 bytes: k2 *= M_32; k2 ^= k2 >> R_32; k2 *= M_32; h *= M_32; h ^= k2; // Do a few final mixes of the hash to ensure the "last few // bytes" are well-incorporated. // TODO: really needed, we have no "last few bytes"? h ^= h >> 13; h *= M_32; h ^= h >> 15; return h; } CYKHASH_INLINE uint32_t murmur2_64to32(uint64_t k){ uint32_t k1=(uint32_t)k; uint32_t k2=(uint32_t)(k>>32); return murmur2_32_32to32(k1, k2); } #else #error Unknown pointer size or missing size macros! #endif #endif """ pass cykhash-2.0.0/src/cykhash/sets/000077500000000000000000000000001414255425400163505ustar00rootroot00000000000000cykhash-2.0.0/src/cykhash/sets/set_header.pxi.in000066400000000000000000000066071414255425400216130ustar00rootroot00000000000000 """ Template for sets WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in """ include "set_init.pxi" {{py: # set_name, name, key_type, c_key_type set_types = [('Int64', 'int64', 'int64_t', 'int64_t'), ('Float64', 'float64', 'float64_t' , 'float64_t'), ('Int32', 'int32', 'int32_t', 'int32_t'), ('Float32', 'float32', 'float32_t', 'float32_t'), ('PyObject', 'pyobject', 'object', 'pyobject_t'), ] }} {{for set_name, name, key_type, c_key_type in set_types}} cdef extern from *: ctypedef struct kh_{{name}}set_t: khint_t n_buckets, size, n_occupied, upper_bound uint32_t *flags {{c_key_type}} *keys #size_t *vals //dummy kh_{{name}}set_t* kh_init_{{name}}set() nogil void kh_destroy_{{name}}set(kh_{{name}}set_t*) nogil void kh_clear_{{name}}set(kh_{{name}}set_t*) nogil khint_t kh_get_{{name}}set(kh_{{name}}set_t*, {{c_key_type}}) nogil void kh_resize_{{name}}set(kh_{{name}}set_t*, khint_t) nogil khint_t kh_put_{{name}}set(kh_{{name}}set_t*, {{c_key_type}}, int*) nogil void kh_del_{{name}}set(kh_{{name}}set_t*, khint_t) nogil #specializing "kh_exist"-macro bint kh_exist_{{name}}set "kh_exist" (kh_{{name}}set_t*, khint_t) nogil cdef class {{set_name}}Set: cdef kh_{{name}}set_t *table cdef bint contains(self, {{key_type}} key) except * cdef {{set_name}}SetIterator get_iter(self) cdef khint_t size(self) cpdef void add(self, {{key_type}} key) except * cpdef void discard(self, {{key_type}} key) except * cdef class {{set_name}}SetIterator: cdef khint_t it cdef {{set_name}}Set parent cdef bint has_next(self) except * {{if set_name == 'PyObject'}} cdef {{key_type}} next(self) {{else}} cdef {{key_type}} next(self) except * {{endif}} cdef void __move(self) except * cpdef {{set_name}}Set {{set_name}}Set_from_buffer({{key_type}}[:] buf, double size_hint=*) from libc.stdint cimport uint8_t cpdef void isin_{{name}}({{key_type}}[:] query, {{set_name}}Set db, uint8_t[:] result) except * cpdef bint all_{{name}}({{key_type}}[:] query, {{set_name}}Set db) except * cpdef bint all_{{name}}_from_iter(object query, {{set_name}}Set db) except * cpdef bint none_{{name}}({{key_type}}[:] query, {{set_name}}Set db) except * cpdef bint none_{{name}}_from_iter(object query, {{set_name}}Set db) except * cpdef bint any_{{name}}({{key_type}}[:] query, {{set_name}}Set db) except * cpdef bint any_{{name}}_from_iter(object query, {{set_name}}Set db) except * cpdef size_t count_if_{{name}}({{key_type}}[:] query, {{set_name}}Set db) except * cpdef size_t count_if_{{name}}_from_iter(object query, {{set_name}}Set db) except * cpdef void swap_{{name}}({{set_name}}Set a, {{set_name}}Set b) except * # for drop-in replacements: cpdef bint aredisjoint_{{name}}({{set_name}}Set a, {{set_name}}Set b) except * cpdef bint issubset_{{name}}({{set_name}}Set s, {{set_name}}Set sub) except * cpdef {{set_name}}Set copy_{{name}}({{set_name}}Set s) cpdef void update_{{name}}({{set_name}}Set s, {{set_name}}Set other) except * cpdef {{set_name}}Set intersect_{{name}}({{set_name}}Set a, {{set_name}}Set b) cpdef {{set_name}}Set difference_{{name}}({{set_name}}Set a, {{set_name}}Set b) cpdef {{set_name}}Set symmetric_difference_{{name}}({{set_name}}Set a, {{set_name}}Set b) {{endfor}} cykhash-2.0.0/src/cykhash/sets/set_impl.pxi.in000066400000000000000000000447331414255425400213260ustar00rootroot00000000000000 """ Template for sets WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in """ from cpython.ref cimport Py_INCREF,Py_DECREF {{py: # set_name, name, key_type set_types = [('Int64', 'int64', 'int64_t'), ('Float64', 'float64', 'float64_t'), ('Int32', 'int32', 'int32_t'), ('Float32', 'float32', 'float32_t'), ('PyObject', 'pyobject', 'object'), ] }} {{for set_name, name, key_type in set_types}} #### special for PyObject: {{if set_name == 'PyObject'}} cdef void _dealloc_pyobject(kh_pyobjectset_t *table) except *: cdef khint_t i = 0 if table is not NULL: for i in range(table.size): if kh_exist_pyobjectset(table, i): Py_DECREF(table.keys[i]) kh_destroy_pyobjectset(table) cdef bint _contains_pyobject(kh_pyobjectset_t *table, object key) nogil: cdef khint_t k k = kh_get_pyobjectset(table, key) return k != table.n_buckets cdef void _add_pyobject(kh_pyobjectset_t *table, object key) except *: cdef: khint_t k int ret = 0 pyobject_t key_ptr = key k = kh_put_pyobjectset(table, key_ptr, &ret) if ret: #element was really added, so we need to increase reference Py_INCREF(key) cdef void _discard_pyobject(kh_pyobjectset_t *table, object key) except *: cdef khint_t k cdef pyobject_t key_ptr = key k = kh_get_pyobjectset(table, key_ptr) if k != table.n_buckets: Py_DECREF(table.keys[k]) kh_del_pyobjectset(table, k) ### Iterator: cdef class PyObjectSetIterator: cdef void __move(self) except *: while self.itresult def __cinit__(self, PyObjectSet parent): self.parent = parent #search the start: self.it = 0 self.__move() def __next__(self): if self.has_next(): return self.next() else: raise StopIteration {{else}} cdef void _dealloc_{{name}}(kh_{{name}}set_t *table) nogil: if table is not NULL: kh_destroy_{{name}}set(table) cdef bint _contains_{{name}}(kh_{{name}}set_t *table, {{key_type}} key) nogil: cdef khint_t k k = kh_get_{{name}}set(table, key) return k != table.n_buckets cdef void _add_{{name}}(kh_{{name}}set_t *table, {{key_type}} key) nogil: cdef: khint_t k int ret = 0 k = kh_put_{{name}}set(table, key, &ret) table.keys[k] = key cdef void _discard_{{name}}(kh_{{name}}set_t *table, {{key_type}} key) nogil: cdef khint_t k k = kh_get_{{name}}set(table, key) if k != table.n_buckets: kh_del_{{name}}set(table, k) ### Iterator: cdef class {{set_name}}SetIterator: cdef void __move(self) except *: while self.it>> from cykhash import {{set_name}}Set >>> info = {{set_name}}Set([1]).get_state_info() >>> info["n_buckets"] 4 >>> info["n_occupied"] 1 """ return {"n_buckets" : self.table.n_buckets, "n_occupied" : self.table.n_occupied, "upper_bound" : self.table.upper_bound} ### drop-in for set: def isdisjoint(self, other): if isinstance(other, {{set_name}}Set): return aredisjoint_{{name}}(self, other) cdef {{key_type}} el for el in other: if self.contains(el): return False return True def issuperset(self, other): if isinstance(other, {{set_name}}Set): return issubset_{{name}}(self, other) cdef {{key_type}} el for el in other: if not self.contains(el): return False return True def issubset(self, other): if isinstance(other, {{set_name}}Set): return issubset_{{name}}(other, self) cdef {{key_type}} el cdef {{set_name}}Set mem={{set_name}}Set() for el in other: if self.contains(el): mem.add(el) return mem.size()==self.size() def __repr__(self): return "{"+','.join(map(str, self))+"}" def __le__(self, {{set_name}}Set other): return issubset_{{name}}(other, self) def __lt__(self, {{set_name}}Set other): return issubset_{{name}}(other, self) and self.size()other.size() def __eq__(self, {{set_name}}Set other): return issubset_{{name}}(self, other) and self.size()==other.size() def __or__(self, {{set_name}}Set other): cdef {{set_name}}Set res = copy_{{name}}(self) update_{{name}}(res, other) return res def __ior__(self, {{set_name}}Set other): update_{{name}}(self, other) return self def __and__(self, {{set_name}}Set other): return intersect_{{name}}(self, other) def __iand__(self, {{set_name}}Set other): cdef {{set_name}}Set res = intersect_{{name}}(self, other) swap_{{name}}(self, res) return self def __sub__(self, {{set_name}}Set other): return difference_{{name}}(self, other) def __isub__(self, {{set_name}}Set other): cdef {{set_name}}Set res = difference_{{name}}(self, other) swap_{{name}}(self, res) return self def __xor__(self, {{set_name}}Set other): return symmetric_difference_{{name}}(self, other) def __ixor__(self, {{set_name}}Set other): cdef {{set_name}}Set res = symmetric_difference_{{name}}(self, other) swap_{{name}}(self, res) return self def copy(self): return copy_{{name}}(self) def union(self, *others): cdef {{set_name}}Set res = copy_{{name}}(self) for o in others: res.update(o) return res def update(self, other): if isinstance(other, {{set_name}}Set): update_{{name}}(self, other) return cdef {{key_type}} el for el in other: self.add(el) def intersection(self, *others): cdef {{set_name}}Set res = copy_{{name}}(self) for o in others: res.intersection_update(o) return res def intersection_update(self, other): cdef {{set_name}}Set res cdef {{key_type}} el if isinstance(other, {{set_name}}Set): res = intersect_{{name}}(self, other) else: res = {{set_name}}Set() for el in other: if self.contains(el): res.add(el) swap_{{name}}(self, res) def difference_update(self, other): cdef {{set_name}}Set res cdef {{key_type}} el if isinstance(other, {{set_name}}Set): res = difference_{{name}}(self, other) swap_{{name}}(self, res) else: for el in other: self.discard(el) def difference(self, *others): cdef {{set_name}}Set res = copy_{{name}}(self) for o in others: res.difference_update(o) return res def symmetric_difference_update(self, other): cdef {{set_name}}Set res cdef {{key_type}} el if isinstance(other, {{set_name}}Set): res = symmetric_difference_{{name}}(self, other) else: res = self.copy() for el in other: if self.contains(el): res.discard(el) else: res.add(el) swap_{{name}}(self, res) def symmetric_difference(self, *others): cdef {{set_name}}Set res = copy_{{name}}(self) for o in others: res.symmetric_difference_update(o) return res def clear(self): cdef {{set_name}}Set res = {{set_name}}Set() swap_{{name}}(self, res) def remove(self, key): cdef size_t old=self.size() self.discard(key) if old==self.size(): raise KeyError(key) def pop(self): if self.size()== 0: raise KeyError("pop from empty set") cdef {{set_name}}SetIterator it = self.get_iter() cdef {{key_type}} el = it.next() self.discard(el) return el ### Utils: def {{set_name}}Set_from(it): """ creates {{set_name}}Set from an iterator. Use {{set_name}}Set_from_buffer for a faster version if iterator is buffer of correct type """ res={{set_name}}Set() for i in it: res.add(i) return res cpdef {{set_name}}Set {{set_name}}Set_from_buffer({{key_type}}[:] buf, double size_hint=0.0): """ creates {{set_name}}Set from the given buffer buf. Use slower {{set_name}}Set_from if series is given as iterator without buffer protocol. size_hint is an estimation of the ratio of unique elements. The default value of 0.0 means all elements in buf are expected to be unique Giving a good estimate will avoid rehashing (if estimate is too low) and having too big table (if estimate is too high). """ cdef Py_ssize_t n = len(buf) cdef Py_ssize_t at_least_needed = element_n_from_size_hint(n, size_hint) res={{set_name}}Set(number_of_elements_hint=at_least_needed) cdef Py_ssize_t i for i in range(n): res.add(buf[i]) return res cpdef void isin_{{name}}({{key_type}}[:] query, {{set_name}}Set db, uint8_t[:] result) except *: """ given query, db writes for every element of query True/False into result depending on whether query-element is in db (=True) or not (=False). result should have the same length as query. """ cdef size_t i cdef size_t n=len(query) if n!=len(result): raise ValueError("Different sizes for query({n}) and result({m})".format(n=n, m=len(result))) for i in range(n): result[i]=db is not None and db.contains(query[i]) cpdef bint all_{{name}}({{key_type}}[:] query, {{set_name}}Set db) except *: """ True if all elements of query are in db, False otherwise. """ if query is None: return True cdef size_t i cdef size_t n=len(query) if db is None: return n==0 for i in range(n): if not db.contains(query[i]): return False return True cpdef bint all_{{name}}_from_iter(object query, {{set_name}}Set db) except *: """ True if all elements of query (as iterator) are in db, False otherwise. """ if query is None: return True cdef {{key_type}} el for el in query: if db is None or not db.contains(el): return False return True cpdef bint none_{{name}}({{key_type}}[:] query, {{set_name}}Set db) except *: """ True if none of elements in query is in db, False otherwise. """ if query is None or db is None: return True cdef size_t i cdef size_t n=len(query) for i in range(n): if db.contains(query[i]): return False return True cpdef bint none_{{name}}_from_iter(object query, {{set_name}}Set db) except *: """ True if none of elements in query (as iterator) is in db, False otherwise. """ if query is None or db is None: return True cdef {{key_type}} el for el in query: if db.contains(el): return False return True cpdef bint any_{{name}}({{key_type}}[:] query, {{set_name}}Set db) except *: """ True if one of elements in query is in db, False otherwise. """ return not none_{{name}}(query, db) cpdef bint any_{{name}}_from_iter(object query, {{set_name}}Set db) except *: """ True if one of elements in query (as iterator) is in db, False otherwise. """ return not none_{{name}}_from_iter(query, db) cpdef size_t count_if_{{name}}({{key_type}}[:] query, {{set_name}}Set db) except *: """ returns the number of (non-unique) elements in query, which are also in db """ if query is None or db is None: return 0 cdef size_t i cdef size_t n=len(query) cdef size_t res=0 for i in range(n): if db.contains(query[i]): res+=1 return res cpdef size_t count_if_{{name}}_from_iter(object query, {{set_name}}Set db) except *: """ returns the number of (non-unique) elements in query (as iter), which are also in db """ if query is None or db is None: return 0 cdef {{key_type}} el cdef size_t res=0 for el in query: if db.contains(el): res+=1 return res cpdef bint aredisjoint_{{name}}({{set_name}}Set a, {{set_name}}Set b) except *: if a is None or b is None: raise TypeError("'NoneType' object is not iterable") cdef {{set_name}}SetIterator it cdef {{set_name}}Set s cdef {{key_type}} el if a.size()itemsize is the _previous_ itemsize. n * itemsize = len still holds at this # point. The equality calcsize(format) = itemsize does _not_ hold # from here on! */ view.format = NULL # data is one-dimensional view.ndim = 1; view.shape = NULL view.strides = NULL view.suboffsets = NULL # no need for internal data view.internal = NULL self.buffer_lock_cnt+=1 def __releasebuffer__(self, buffer.Py_buffer *view): self.buffer_lock_cnt-=1 @staticmethod cdef MemoryNanny create_memory_nanny(void* ptr, Py_ssize_t n, Py_ssize_t element_size, object format): cdef MemoryNanny nanny = MemoryNanny() nanny.ptr = ptr nanny.n = n nanny.element_size = element_size nanny.format = format return nanny include "unique/unique_impl.pxi" cykhash-2.0.0/src/cykhash/unique/000077500000000000000000000000001414255425400167005ustar00rootroot00000000000000cykhash-2.0.0/src/cykhash/unique/unique_impl.pxi.in000066400000000000000000000036571414255425400223710ustar00rootroot00000000000000 """ Template for sets WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in """ {{py: # set_name, name, key_type, format set_types = [('Int64', 'int64', 'int64_t', '"q"'), ('Float64', 'float64', 'float64_t', '"d"'), ('Int32', 'int32', 'int32_t', '"i"'), ('Float32', 'float32', 'float32_t', '"f"'), ] }} {{for set_name, name, key_type, format in set_types}} cpdef unique_{{name}}({{key_type}}[:] vals, double size_hint=0.0): cdef {{set_name}}Set s = {{set_name}}Set_from_buffer(vals, size_hint) # compress: cdef {{key_type}}* mem = s.table.keys cdef khint_t i cdef khint_t current = 0 for i in range(s.table.n_buckets): if kh_exist_{{name}}set(s.table, i): mem[current] = mem[i] current += 1 # take over the memory: s.table.keys = NULL # shrink to fit: mem = <{{key_type}}*> cykhash_traced_realloc(mem, sizeof({{key_type}})*current); return MemoryNanny.create_memory_nanny(mem, current, sizeof({{key_type}}), b{{format}}) cpdef unique_stable_{{name}}({{key_type}}[:] vals, double size_hint=0.0): # prepare cdef Py_ssize_t n = len(vals) cdef Py_ssize_t at_least_needed = element_n_from_size_hint(n, size_hint) res={{set_name}}Set(number_of_elements_hint=at_least_needed) cdef {{key_type}}* mem = <{{key_type}}*> cykhash_traced_malloc(sizeof({{key_type}})*n); # insert cdef khint_t current = 0 cdef Py_ssize_t i cdef {{key_type}} element for i in range(n): element = vals[i] res.add(element) if current != res.size(): mem[current] = element current += 1 # shrink to fit: mem = <{{key_type}}*> cykhash_traced_realloc(mem, sizeof({{key_type}})*current); return MemoryNanny.create_memory_nanny(mem, current, sizeof({{key_type}}), b{{format}}) {{endfor}} cykhash-2.0.0/src/cykhash/utils.pyx000066400000000000000000000036031414255425400172760ustar00rootroot00000000000000from libc.stdint cimport uint64_t, uint32_t, int64_t, int32_t from .floatdef cimport float64_t, float32_t include "common.pxi" include "memory.pxi" def get_cykhash_trace_domain(): """ yield domain number of the cykhash trace domain (as specified by trace malloc), using this trace domain it is possible to trace memory allocations done by cykhash """ return CYKHASH_TRACE_DOMAIN include "hash_functions.pxi" cdef extern from *: """ // from hash_functions.pxi """ uint32_t cykh_float32_hash_func(float val) uint32_t cykh_float64_hash_func(double val) uint32_t cykh_int32_hash_func(uint32_t val) uint32_t cykh_int64_hash_func(uint64_t val) uint32_t cykh_pyobject_hash_func(object ob) bint pyobject_cmp(object a, object b) # some utils useful for investigations def objects_are_equal(a, b): """ returns true if both objects are considered equal for khash-set/map """ return pyobject_cmp(a, b) def float64_hash(double val): """ returns hash used for float64-values by cykhash sets/maps >>> from cykhash.utils import float64_hash >>> float64_hash(0.0) 0 >>> float64_hash(-0.0) 0 """ return cykh_float64_hash_func(val) def float32_hash(float val): """ returns hash used for float32-values by cykhash sets/maps >>> from cykhash.utils import float32_hash >>> float32_hash(0.0) 0 >>> float32_hash(-0.0) 0 """ return cykh_float32_hash_func(val) def object_hash(val): """ returns hash used for objects by cykhash sets/maps """ return cykh_pyobject_hash_func(val) def int64_hash(int64_t val): """ returns hash used for int64-values by cykhash sets/maps """ return cykh_int64_hash_func(val) def int32_hash(int32_t val): """ returns hash used for int32-values by cykhash sets/maps """ return cykh_int32_hash_func(val) cykhash-2.0.0/tests/000077500000000000000000000000001414255425400143135ustar00rootroot00000000000000cykhash-2.0.0/tests/asv_bench/000077500000000000000000000000001414255425400162435ustar00rootroot00000000000000cykhash-2.0.0/tests/asv_bench/asv.conf.json000066400000000000000000000101341414255425400206520ustar00rootroot00000000000000{ // The version of the config file format. Do not change, unless // you know what you are doing. "version": 1, // The name of the project being benchmarked "project": "cykhash", // The project's homepage "project_url": "https://github.com/realead/cykhash", // The URL of the source code repository for the project being // benchmarked "repo": "../..", // The tool to use to create environments. May be "conda", // "virtualenv" or other value depending on the plugins in use. // If missing or the empty string, the tool will be automatically // determined by looking for tools on the PATH environment // variable. "environment_type": "conda", // the base URL to show a commit for the project. "show_commit_url": "https://github.com/realead/cykhash/commit/", // The Pythons you'd like to test against. If not provided, defaults // to the current version of Python used to run `asv`. // "pythons": [], // The matrix of dependencies to test. Each key is the name of a // package (in PyPI) and the values are version numbers. An empty // list or empty string indicates to just test against the default // (latest) version. null indicates that the package is to not be // installed. If the package to be tested is only available from // PyPi, and the 'environment_type' is conda, then you can preface // the package name by 'pip+', and the package will be installed via // pip (with all the conda available packages installed first, // followed by the pip installed packages). "matrix": { "numpy": [], "Cython": [], }, "conda_channels": ["defaults", "conda-forge"], // Combinations of libraries/python versions can be excluded/included // from the set to test. Each entry is a dictionary containing additional // key-value pairs to include/exclude. // // An exclude entry excludes entries where all values match. The // values are regexps that should match the whole string. // // An include entry adds an environment. Only the packages listed // are installed. The 'python' key is required. The exclude rules // do not apply to includes. // // In addition to package names, the following keys are available: // // - python // Python version, as in the *pythons* variable above. // - environment_type // Environment type, as above. // - sys_platform // Platform, as in sys.platform. Possible values for the common // cases: 'linux2', 'win32', 'cygwin', 'darwin'. "exclude": [], "include": [], // The directory (relative to the current directory) that benchmarks are // stored in. If not provided, defaults to "benchmarks" // "benchmark_dir": "benchmarks", // The directory (relative to the current directory) to cache the Python // environments in. If not provided, defaults to "env" // "env_dir": "env", // The directory (relative to the current directory) that raw benchmark // results are stored in. If not provided, defaults to "results". // "results_dir": "results", // The directory (relative to the current directory) that the html tree // should be written to. If not provided, defaults to "html". // "html_dir": "html", // The number of characters to retain in the commit hashes. // "hash_length": 8, // `asv` will cache wheels of the recent builds in each // environment, making them faster to install next time. This is // number of builds to keep, per environment. "build_cache_size": 8, // The commits after which the regression search in `asv publish` // should start looking for regressions. Dictionary whose keys are // regexps matching to benchmark names, and values corresponding to // the commit (exclusive) after which to start looking for // regressions. The default is to start from the first commit // with results. If the commit is `null`, regression detection is // skipped for the matching benchmark. // "regressions_first_commits": { }, "regression_thresholds": { }, } cykhash-2.0.0/tests/asv_bench/benchmarks/000077500000000000000000000000001414255425400203605ustar00rootroot00000000000000cykhash-2.0.0/tests/asv_bench/benchmarks/__init__.py000066400000000000000000000000001414255425400224570ustar00rootroot00000000000000cykhash-2.0.0/tests/asv_bench/benchmarks/count_if.py000066400000000000000000000061531414255425400225450ustar00rootroot00000000000000import numpy as np from cykhash import count_if_int64, count_if_int64_from_iter, Int64Set_from, Int64Set_from_buffer from cykhash import count_if_int32, count_if_int32_from_iter, Int32Set_from, Int32Set_from_buffer from cykhash import count_if_float64, count_if_float64_from_iter, Float64Set_from, Float64Set_from_buffer from cykhash import count_if_float32, count_if_float32_from_iter, Float32Set_from, Float32Set_from_buffer from cykhash import count_if_pyobject, count_if_pyobject_from_iter, PyObjectSet_from, PyObjectSet_from_buffer CREATE_SET={ np.float64 : Float64Set_from_buffer, np.float32 : Float32Set_from_buffer, np.int64 : Int64Set_from_buffer, np.int32 : Int32Set_from_buffer, } COUNT_IF = { np.float64 : count_if_float64, np.float32 : count_if_float32, np.int64 : count_if_int64, np.int32 : count_if_int32, } class CountIfObject: def setup(self): N=100_000 self.set = PyObjectSet_from(x<<32 for x in range(N)) np.random.seed(42) self.query = np.random.randint(0,N,N).astype(np.object) def time_countif(self): count_if_pyobject(self.query, self.set) class CountIfSameLongTuple: def setup(self): t = tuple(range(1000)) self.set = PyObjectSet_from([t]) self.query = np.array(["a"] + [t]*1000) def time_countif(self): count_if_pyobject(self.query, self.set) class CountIfArange: params = [ [np.float64, np.float32, np.int64, np.int32], [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000], [-2, 0, 2] ] param_names = ["dtype", "M", "offset_factor"] def setup(self, dtype, M, offset_factor): self.set = CREATE_SET[dtype](np.arange(M).astype(dtype)) offset = int(M*offset_factor) N=10**6 np.random.seed(42) self.query = np.random.randint(offset,M+offset,N).astype(dtype) def time_countif(self, dtype, M, offset_factor): COUNT_IF[dtype](self.query, self.set) class CountIfRandomYes: params = [ [np.float64, np.float32, np.int64, np.int32], # [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): np.random.seed(42) keys = (np.random.rand(M)*M).astype(dtype) self.set = CREATE_SET[dtype](keys) N=10**6 self.query = (np.random.rand(N)*M).astype(dtype) def time_countif(self, dtype, M): COUNT_IF[dtype](self.query, self.set) class CountIfRandomNo: params = [ [np.float64, np.float32, np.int64, np.int32], # [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): np.random.seed(42) keys = (np.random.rand(M)*M).astype(dtype) self.set = CREATE_SET[dtype](keys) N=10**6 self.query = (np.random.rand(N)*M+2*M).astype(dtype) def time_countif(self, dtype, M): COUNT_IF[dtype](self.query, self.set) cykhash-2.0.0/tests/asv_bench/benchmarks/map_methods.py000066400000000000000000000075411414255425400232410ustar00rootroot00000000000000import numpy as np import cykhash as cyk MAP_TO_INT = {np.int32: cyk.Int32toInt32Map_to, np.int64: cyk.Int64toInt64Map_to, np.float64 : cyk.Float64toInt64Map_to, np.float32 : cyk.Float32toInt32Map_to, np.object : cyk.PyObjectMap_to, } CREATOR_FROM_INT = {np.int32: cyk.Int32toInt32Map_from_buffers, np.int64: cyk.Int64toInt64Map_from_buffers, np.float64 : cyk.Float64toInt64Map_from_buffers, np.float32 : cyk.Float32toInt32Map_from_buffers, np.object : cyk.PyObjectMap_from_buffers, } INT_DTYPE = {np.int32: np.int32, np.int64: np.int64, np.float64 : np.int64, np.float32 : np.int32, np.object : np.object, } class MapToWithArange: params = [ [np.float64, np.float32, np.int64, np.int32, np.object], # [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000], #problem when quadratic behavior is triggered: [10, 100, 1000, 2_000, 8_000, 10_000, 100_000, 256_000, 1_000_000, 10_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): keys = np.arange(M).astype(dtype) vals = np.zeros_like(keys, dtype=INT_DTYPE[dtype]) self.map = CREATOR_FROM_INT[dtype](keys, vals) self.query = np.repeat(keys, 5) self.result = np.ones_like(self.query, dtype=INT_DTYPE[dtype]) def time_mapto(self, dtype, M): MAP_TO_INT[dtype](self.map, self.query, self.result, False) class MapToWithRandom: params = [ [np.float64, np.float32, np.int64, np.int32, np.object], # [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000], #problem when quadratic behavior is triggered: [10, 100, 1000, 2_000, 8_000, 10_000, 100_000, 256_000, 1_000_000, 10_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): keys = np.arange(M).astype(dtype) vals = np.zeros_like(keys, dtype=INT_DTYPE[dtype]) self.map = CREATOR_FROM_INT[dtype](keys, vals) np.random.seed(42) self.query = np.random.randint(0, M, 5*M).astype(dtype) self.result = np.ones_like(self.query, dtype=INT_DTYPE[dtype]) def time_mapto(self, dtype, M): MAP_TO_INT[dtype](self.map, self.query, self.result, False) class MapToWithRandomFloat: params = [ [np.float64, np.float32], # [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000], #problem when quadratic behavior is triggered: [10, 100, 1000, 2_000, 8_000, 10_000, 100_000, 256_000, 1_000_000, 10_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): np.random.seed(42) keys = np.arange(M).astype(dtype) vals = np.zeros_like(keys, dtype=INT_DTYPE[dtype]) self.map = CREATOR_FROM_INT[dtype](keys, vals) self.query = np.repeat(keys, 5) self.result = np.ones_like(self.query, dtype=INT_DTYPE[dtype]) def time_mapto(self, dtype, M): MAP_TO_INT[dtype](self.map, self.query, self.result, False) class MapWithRandomScaledFloat: params = [ [np.float64, np.float32], # [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000], #problem when quadratic behavior is triggered: [10, 100, 1000, 2_000, 8_000, 10_000, 100_000, 256_000, 1_000_000, 10_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): np.random.seed(42) keys = (np.random.rand(M) * 100*M).astype(dtype) vals = np.zeros_like(keys, dtype=INT_DTYPE[dtype]) self.map = CREATOR_FROM_INT[dtype](keys, vals) self.query = np.repeat(keys, 5) self.result = np.ones_like(self.query, dtype=INT_DTYPE[dtype]) def time_mapto(self, dtype, M): MAP_TO_INT[dtype](self.map, self.query, self.result, False) cykhash-2.0.0/tests/asv_bench/benchmarks/set_methods.py000066400000000000000000000043061414255425400232530ustar00rootroot00000000000000import numpy as np from cykhash import count_if_int64, count_if_int64_from_iter, Int64Set_from, Int64Set_from_buffer from cykhash import count_if_int32, count_if_int32_from_iter, Int32Set_from, Int32Set_from_buffer from cykhash import count_if_float64, count_if_float64_from_iter, Float64Set_from, Float64Set_from_buffer from cykhash import count_if_float32, count_if_float32_from_iter, Float32Set_from, Float32Set_from_buffer from cykhash import count_if_pyobject, count_if_pyobject_from_iter, PyObjectSet_from, PyObjectSet_from_buffer CREATE_SET={ np.float64 : Float64Set_from_buffer, np.float32 : Float32Set_from_buffer, np.int64 : Int64Set_from_buffer, np.int32 : Int32Set_from_buffer, } class CreateArange: params = [ [np.float64, np.float32, np.int64, np.int32], # [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000], #problem when quadratic behavior is triggered: [10, 100, 1000, 2_000, 8_000, 10_000, 100_000, 256_000, 1_000_000, 10_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): self.keys = np.arange(M).astype(dtype) def time_create(self, dtype, M): CREATE_SET[dtype](self.keys) class CreateRandom: params = [ [np.float64, np.float32], # [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000], #problem when quadratic behavior is triggered: [10, 100, 1000, 2_000, 8_000, 10_000, 100_000, 256_000, 1_000_000, 10_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): np.random.seed(42) self.keys = np.random.rand(M).astype(dtype) def time_create(self, dtype, M): CREATE_SET[dtype](self.keys) class CreateRandomScaled: params = [ [np.float64, np.float32, np.int64, np.int32], # [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000], #problem when quadratic behavior is triggered: [10, 100, 1000, 2_000, 8_000, 10_000, 100_000, 256_000, 1_000_000, 10_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): np.random.seed(42) self.keys = (np.random.rand(M) * 100*M).astype(dtype) def time_create(self, dtype, M): CREATE_SET[dtype](self.keys) cykhash-2.0.0/tests/asv_bench/benchmarks/unique.py000066400000000000000000000064131414255425400222440ustar00rootroot00000000000000import numpy as np from cykhash import unique_int64, unique_int32, unique_float64, unique_float32 from cykhash import unique_stable_int64, unique_stable_int32, unique_stable_float64, unique_stable_float32 UNIQUE={ np.float64 : unique_float64, np.float32 : unique_float32, np.int64 : unique_int64, np.int32 : unique_int32, } UNIQUE_STABLE = { np.float64 : unique_stable_float64, np.float32 : unique_stable_float32, np.int64 : unique_stable_int64, np.int32 : unique_stable_int32, } class UniqueArange: params = [ [np.float64, np.float32, np.int64, np.int32], [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): self.array = np.arange(M, dtype=dtype) def time_unique(self, dtype, M): UNIQUE[dtype](self.array) def time_unique_stable(self, dtype, M): UNIQUE_STABLE[dtype](self.array) def peakmem_unique(self, dtype, M): UNIQUE[dtype](self.array) def peakmem_unique_stable(self, dtype, M): UNIQUE_STABLE[dtype](self.array) class UniqueRandomDivFactor10: params = [ [np.float64, np.float32, np.int64, np.int32], [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): np.random.seed(42) self.array = np.random.randint(0, M//10, M).astype(dtype) def time_unique(self, dtype, M): UNIQUE[dtype](self.array) def time_unique_stable(self, dtype, M): UNIQUE_STABLE[dtype](self.array) def peakmem_unique(self, dtype, M): UNIQUE[dtype](self.array) def peakmem_unique_stable(self, dtype, M): UNIQUE_STABLE[dtype](self.array) class UniqueRandomDivFactor10Add220: params = [ [np.float64, np.float32, np.int64, np.int32], [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): np.random.seed(42) self.array = (np.random.randint(0, M//10, M)+2**26).astype(dtype) def time_unique(self, dtype, M): UNIQUE[dtype](self.array) def time_unique_stable(self, dtype, M): UNIQUE_STABLE[dtype](self.array) class UniqueRandomMulFactor10: params = [ [np.float64, np.float32, np.int64, np.int32], [1_000, 2_000, 8_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): np.random.seed(42) self.array = np.random.randint(0, M*10, M).astype(dtype) def time_unique(self, dtype, M): UNIQUE[dtype](self.array) def time_unique_stable(self, dtype, M): UNIQUE_STABLE[dtype](self.array) class UniqueSingle: params = [ [np.float64, np.float32, np.int64, np.int32], [10_000_000, 100_000_000], ] param_names = ["dtype", "M"] def setup(self, dtype, M): self.array = np.ones(M, dtype=dtype) def peakmem_unique(self, dtype, M): UNIQUE[dtype](self.array) def peakmem_unique_stable(self, dtype, M): UNIQUE_STABLE[dtype](self.array) cykhash-2.0.0/tests/perf_tests/000077500000000000000000000000001414255425400164715ustar00rootroot00000000000000cykhash-2.0.0/tests/perf_tests/cyunique.pyx000066400000000000000000000007741414255425400211050ustar00rootroot00000000000000cimport numpy as np import numpy as np from cykhash.khashsets cimport Int64Set, Int64SetIterator def unique_int64(np.int64_t[::1] data): cdef np.ndarray[dtype=np.int64_t] res cdef Int64Set s=Int64Set(len(data)) cdef Int64SetIterator it cdef Py_ssize_t i cdef int cnt=0 for i in range(len(data)): s.add(data[i]) res=np.empty(s.table.size, dtype=np.int64) it = s.get_iter() for i in range(s.table.size): res[cnt]=it.next() cnt+=1 return res cykhash-2.0.0/tests/perf_tests/cyunique_test.py000066400000000000000000000003641414255425400217470ustar00rootroot00000000000000import pyximport; import numpy as np pyximport.install(setup_args={'include_dirs': np.get_include()}) import cyunique import sys N=int(sys.argv[1]) a=np.arange(N, dtype=np.int64) b=cyunique.unique_int64(a) print("cyunique LEN:", len(b)) cykhash-2.0.0/tests/perf_tests/isin_test.py000066400000000000000000000012421414255425400210430ustar00rootroot00000000000000import numpy as np import pandas as pd import timeit from cykhash import isin_int64, Int64Set_from np.random.seed(0) arr = np.random.randint(0, 20000, 10000) res = np.zeros(arr.shape, np.uint8) ser = pd.Series(arr) NUMBER=100 print("n\tpandas(#look-up=10^n)\tcykhash(#look-up=10^n)") for i in range(2,8): x_arr = np.array(range(10**i)) int64set = Int64Set_from(range(10**i)) t1 = timeit.timeit("ser.isin(x_arr)", setup="from __main__ import ser, x_arr", number=NUMBER)/NUMBER t2 = timeit.timeit("isin_int64(ser.values, int64set, res)", setup = "from __main__ import isin_int64, ser, int64set, res", number=NUMBER)/NUMBER print(i,"\t",t1,"\t",t2) cykhash-2.0.0/tests/perf_tests/khashunique_vs_pdunique.py000066400000000000000000000014361414255425400240160ustar00rootroot00000000000000import perfplot import numpy as np import pandas as pd from cykhash import unique_int64, unique_int32 def pandas_unique64(bufs): pd.unique(bufs[0]) def pandas_unique32(bufs): pd.unique(bufs[1]) def cykhash_unique64(bufs): unique_int64(bufs[0]) def cykhash_unique32(bufs): unique_int32(bufs[1]) if True: perfplot.show( setup = lambda n : (np.arange(n, dtype=np.int64), np.arange(n, dtype=np.int32)), n_range=[2**k for k in range(5,20)], kernels=[ pandas_unique64, pandas_unique32, cykhash_unique64, cykhash_unique32, ], logx=False, logy=False, xlabel='number of elements', title = "pd.unique vs cykhash.unique", equality_check = None, ) cykhash-2.0.0/tests/perf_tests/map_object_vs_int64_via_buffer.py000066400000000000000000000033001414255425400250660ustar00rootroot00000000000000import perfplot import numpy as np from cykhash import Int64to64Map, Int64to64MapIterator, Int64to64Map_from_int64_buffer, Int64to64Map_from_float64_buffer from cykhash import Int32to32Map, Int32to32MapIterator, Int32to32Map_from_int32_buffer, Int32to32Map_from_float32_buffer from cykhash import Float64to64Map, Float64to64MapIterator, Float64to64Map_from_int64_buffer, Float64to64Map_from_float64_buffer from cykhash import Float32to32Map, Float32to32MapIterator, Float32to32Map_from_int32_buffer, Float32to32Map_from_float32_buffer from cykhash import PyObjectMap, PyObjectMapIterator, PyObjectMap_from_object_buffer def pyobjectset_from_buffer(bufs): PyObjectMap_from_object_buffer(bufs[0], bufs[0]) def pyobjectset_add_preallocated(bufs): n = len(bufs[0]) p = PyObjectMap(int(1.3*n)) for i in range(n): p[i] = i def int64_add_preallocated(bufs): n = len(bufs[1]) p = Int64to64Map(int(1.3*n)) for i in range(n): p[i] = i def int64set_from_buffer(bufs): Int64to64Map_from_int64_buffer(bufs[1], bufs[1]) def int32set_from_buffer(bufs): Int32to32Map_from_int32_buffer(bufs[2], bufs[2]) if True: perfplot.show( setup = lambda n : (np.arange(n, dtype = np.object), np.arange(n, dtype=np.int64), np.arange(n, dtype=np.int32)), n_range=[2**k for k in range(18)], kernels=[ pyobjectset_from_buffer, pyobjectset_add_preallocated, int64_add_preallocated, int64set_from_buffer, int32set_from_buffer, ], logx=False, logy=False, xlabel='number of operations', title = "pyobject_map vs dict", equality_check = None, ) cykhash-2.0.0/tests/perf_tests/mem_test_khash.py000066400000000000000000000001531414255425400220350ustar00rootroot00000000000000import sys from cykhash import Int64Set N=int(sys.argv[1]) s=Int64Set() for i in range(N): s.add(i) cykhash-2.0.0/tests/perf_tests/mem_test_set.py000066400000000000000000000001111414255425400215240ustar00rootroot00000000000000import sys N=int(sys.argv[1]) s=set() for i in range(N): s.add(i) cykhash-2.0.0/tests/perf_tests/memory_unique_test.py000066400000000000000000000012151414255425400227770ustar00rootroot00000000000000import numpy as np import pandas as pd import sys import resource import psutil from cykhash import unique_int64, unique_int32 fun_name = sys.argv[1] N=int(sys.argv[2]) a=np.arange(N, dtype=np.int64) process = psutil.Process() old = process.memory_info().rss old_max = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss * 1024 if fun_name == "pandas": b=pd.unique(a) else: b=np.frombuffer(memoryview(unique_int64(a))) new_max = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss * 1024 if new_max>old_max: overhead_in_bytes = new_max - old print(len(b), overhead_in_bytes/float(N*8) ) else: print(len(b), "too small") cykhash-2.0.0/tests/perf_tests/object_vs_int64_via_buffer.py000066400000000000000000000030431414255425400242350ustar00rootroot00000000000000import perfplot import numpy as np from cykhash import isin_int64, Int64Set_from, Int64Set_from_buffer, Int64Set from cykhash import isin_int32, Int32Set_from, Int32Set_from_buffer from cykhash import isin_float64, Float64Set_from, Float64Set_from_buffer from cykhash import isin_float32, Float32Set_from, Float32Set_from_buffer from cykhash import isin_pyobject, PyObjectSet_from, PyObjectSet_from_buffer, PyObjectSet def pyobjectset_from_buffer(bufs): PyObjectSet_from_buffer(bufs[0]) def pyobjectset_from_iter(bufs): PyObjectSet_from(bufs[0]) def pyobjectset_add_preallocated(bufs): n = len(bufs[1]) p = PyObjectSet(int(1.3*n)) for i in range(n): p.add(i) def int64set_add_preallocated(bufs): n = len(bufs[1]) p = Int64Set(int(1.3*n)) for i in range(n): p.add(i) def int64set_from_buffer(bufs): Int64Set_from_buffer(bufs[1]) def int32set_from_buffer(bufs): Int32Set_from_buffer(bufs[2]) if True: perfplot.show( setup = lambda n : (np.arange(n, dtype = np.object), np.arange(n, dtype=np.int64), np.arange(n, dtype=np.int32)), n_range=[2**k for k in range(18)], kernels=[ pyobjectset_from_buffer, pyobjectset_from_iter, pyobjectset_add_preallocated, int64set_add_preallocated, int64set_from_buffer, int32set_from_buffer, ], logx=False, logy=False, xlabel='number of operations', title = "pyobject_set vs set", equality_check = None, ) cykhash-2.0.0/tests/perf_tests/pandas_unique_test.py000066400000000000000000000002251414255425400227350ustar00rootroot00000000000000import numpy as np import pandas as pd import sys N=int(sys.argv[1]) a=np.arange(N, dtype=np.int64) b=pd.unique(a) print("pandas LEN:", len(b)) cykhash-2.0.0/tests/perf_tests/pyobjectmap_vs_dict.py000066400000000000000000000040251414255425400230740ustar00rootroot00000000000000import perfplot from cykhash import PyObjectMap def setmeup(n): print(n) s = dict() p = PyObjectMap() for i in range(n): s[i] = i p[i] = i return (s,p) def contains_dict(maps): s = maps[0] n = len(s)//2 for i in range(n, 3*n): i in s def contains_pyobjectmap(maps): p = maps[1] n = len(p)//2 for i in range(n, 3*n): i in p def discard_insert_dict(maps): s = maps[0] n = len(s) for i in range(n): del s[i] for i in range(n): s[i] = i def discard_insert_pyobjectmap(maps): p = maps[1] n = len(p) for i in range(n): p.discard(i) for i in range(n): p[i] = i def insert_dict(maps): n = len(maps[1]) s = dict() for i in range(n): s[i] = i def insert_pyobjectmap(maps): p = PyObjectMap() n = len(maps[0]) for i in range(n): p[i] = i def insert_pyobjectmap_preallocated(maps): n = len(maps[1]) p = PyObjectMap(int(1.3*n)) for i in range(n): p[i] = i if True: perfplot.show( setup = setmeup, n_range=[2**k for k in range(18)], kernels=[ #insert_set, #insert_pyobjectset, contains_dict, contains_pyobjectmap, discard_insert_dict, discard_insert_pyobjectmap, ], logx=False, logy=False, xlabel='number of operations', title = "pyobject_map vs dict", equality_check = None, ) if True: perfplot.show( setup = setmeup, n_range=[2**k for k in range(18)], kernels=[ insert_dict, insert_pyobjectmap, insert_pyobjectmap_preallocated, #contains_set, #contains_pyobjectset, #discard_insert_set, #discard_insert_pyobjectset, ], logx=False, logy=False, xlabel='number of operations', title = "pyobject_map vs dict", equality_check = None, ) cykhash-2.0.0/tests/perf_tests/pyobjectset_vs_set.py000066400000000000000000000040201414255425400227550ustar00rootroot00000000000000import perfplot from cykhash import PyObjectSet def setmeup(n): print(n) s = set() p = PyObjectSet() for i in range(n): s.add(i) p.add(i) return (s,p) def contains_set(sets): s = sets[0] n = len(s)//2 for i in range(n, 3*n): i in s def contains_pyobjectset(sets): p = sets[1] n = len(p)//2 for i in range(n, 3*n): i in p def discard_insert_set(sets): s = sets[0] n = len(s) for i in range(n): s.discard(i) for i in range(n): s.add(i) def discard_insert_pyobjectset(sets): p = sets[1] n = len(p) for i in range(n): p.discard(i) for i in range(n): p.add(i) def insert_set(sets): n = len(sets[1]) s = set() for i in range(n): s.add(i) def insert_pyobjectset(sets): p = PyObjectSet() n = len(sets[0]) for i in range(n): p.add(i) def insert_pyobjectset_preallocated(sets): n = len(sets[1]) p = PyObjectSet(int(1.3*n)) for i in range(n): p.add(i) if True: perfplot.show( setup = setmeup, n_range=[2**k for k in range(18)], kernels=[ #insert_set, #insert_pyobjectset, contains_set, contains_pyobjectset, discard_insert_set, discard_insert_pyobjectset, ], logx=False, logy=False, xlabel='number of operations', title = "pyobject_set vs set", equality_check = None, ) if True: perfplot.show( setup = setmeup, n_range=[2**k for k in range(18)], kernels=[ insert_set, insert_pyobjectset, insert_pyobjectset_preallocated, #contains_set, #contains_pyobjectset, #discard_insert_set, #discard_insert_pyobjectset, ], logx=False, logy=False, xlabel='number of operations', title = "pyobject_set vs set", equality_check = None, ) cykhash-2.0.0/tests/perf_tests/run_memory_unique_test.sh000066400000000000000000000004631414255425400236510ustar00rootroot00000000000000 K="000" M="000000" for fun_name in "pandas" "cykhash" do echo $fun_name echo "number of elements\t overhead factor" for n in "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" do python memory_unique_test.py $fun_name $n$M done done cykhash-2.0.0/tests/run_asv_bench.sh000066400000000000000000000006531414255425400174670ustar00rootroot00000000000000cd asv_bench # Examples: # # Run all: # python -m asv -f 1.01 upstream/master HEAD # # Only from file #python -m asv continuous -f 1.01 upstream/master HEAD -b ^count_if # # Only classes (which starts with something) from files: # python -m asv continuous -f 1.01 upstream/master HEAD -b ^count_if.CountIf -b ^set_methods.CreateArange python -m asv continuous --quick -f 1.01 HEAD~1 HEAD -b ^count_if.CountIfSameLongTuple cykhash-2.0.0/tests/run_asv_bench_quick.sh000066400000000000000000000006731414255425400206650ustar00rootroot00000000000000cd asv_bench # Examples: # # Run all: # python -m asv -f 1.01 upstream/master HEAD # # Only from file #python -m asv continuous -f 1.01 upstream/master HEAD -b ^count_if # # Only classes (which starts with something) from files: # python -m asv continuous -f 1.01 upstream/master HEAD -b ^count_if.CountIf -b ^set_methods.CreateArange #python -m asv continuous -f 1.01 HEAD HEAD -b ^map_methods python -m asv run --quick -b ^map_methods cykhash-2.0.0/tests/run_doctests.sh000066400000000000000000000003201414255425400173560ustar00rootroot00000000000000echo "\n\nTesting md-files....": (cd .. && pytest --ignore=tests --doctest-glob=*.md -vv --doctest-continue-on-failure) echo "\n\nTesting pyx and pxi from installation..." python run_installed_doctests.py cykhash-2.0.0/tests/run_installed_doctests.py000066400000000000000000000005271414255425400214440ustar00rootroot00000000000000import subprocess import os import cykhash dir_name = os.path.dirname(cykhash.__file__) print("Cykhash installation/doctests search path is:", dir_name) args = ["--doctest-glob=*.pyx", "--doctest-glob=*.pxi", "-vv", "--doctest-continue-on-failure", ] command=["pytest"]+args+[dir_name] subprocess.run(command) cykhash-2.0.0/tests/run_perf_tests.sh000066400000000000000000000013161414255425400177120ustar00rootroot00000000000000WRAPPER="/usr/bin/time -fpeak_used_memory:%M(Kb)" pip install numpy pip install pandas echo "\n\n-----Testing build-in test set\n\n" for N in "1000" "10000" "100000" "1000000" "10000000" do $WRAPPER python perf_tests/mem_test_set.py $N done echo "\n\n-----Testing build-in khash-set\n\n" for N in "1000" "10000" "100000" "1000000" "10000000" do $WRAPPER python perf_tests/mem_test_khash.py $N done echo "\n\n-----Testing pandas isin\n\n" python perf_tests/isin_test.py echo "\n\n-----Testing pandas unique\n\n" for N in "10000000" "20000000" "40000000" "60000000" "80000000" do $WRAPPER python perf_tests/pandas_unique_test.py $N $WRAPPER python perf_tests/cyunique_test.py $N done cykhash-2.0.0/tests/run_unit_tests.sh000066400000000000000000000000351414255425400177320ustar00rootroot00000000000000 (cd unit_tests && pytest) cykhash-2.0.0/tests/test_in_active_env.sh000066400000000000000000000001051414255425400205130ustar00rootroot00000000000000(cd .. && pip install -e .) sh run_unit_tests.sh sh run_doctests.sh cykhash-2.0.0/tests/test_install.sh000066400000000000000000000016171414255425400173610ustar00rootroot00000000000000set -e ENV_DIR="../p3" virtualenv -p python3 "$ENV_DIR" echo "Testing python3" #activate environment . "$ENV_DIR/bin/activate" python -c "import setuptools; print('setuptools version:', setuptools.__version__)" # test installation in clean environment if [ "$1" = "from-github" ]; then pip install https://github.com/realead/cykhash/zipball/master elif [ "$1" = "from-test-pypi" ]; then pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple cykhash elif [ "$1" = "from-pypi" ]; then pip install cykhash else (cd .. && python -m pip install .) # (cd .. && python setup.py build install) fi; # needed for testing: pip install numpy pip install cython pip install pytest pip freeze sh run_unit_tests.sh sh run_doctests.sh #clean or keep the environment if [ "$2" = "keep" ]; then echo "keeping enviroment $ENV_DIR" else rm -r "$ENV_DIR" fi; cykhash-2.0.0/tests/unit_tests/000077500000000000000000000000001414255425400165145ustar00rootroot00000000000000cykhash-2.0.0/tests/unit_tests/cyinterfacetester.pyx000066400000000000000000000051721414255425400230060ustar00rootroot00000000000000 ############# int64 - test from cykhash.khashsets cimport Int64Set, Int64SetIterator, isin_int64, Int64Set_from_buffer def py_isin_int64(query, db): s=Int64Set() for d in db: s.add(d) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(False if s.contains(i)==0 else True) return res def as_py_set_int64(Int64Set db): cdef Int64SetIterator it = db.get_iter() res=set() while it.has_next(): res.add(it.next()) return res ############# int32 - test from cykhash.khashsets cimport Int32Set, Int32SetIterator, isin_int32, Int32Set_from_buffer def py_isin_int32(query, db): s=Int32Set() for d in db: s.add(d) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(False if s.contains(i)==0 else True) return res def as_py_set_int32(Int32Set db): cdef Int32SetIterator it = db.get_iter() res=set() while it.has_next(): res.add(it.next()) return res ############# float64 - test from cykhash.khashsets cimport Float64Set, Float64SetIterator, isin_float64, Float64Set_from_buffer def py_isin_float64(query, db): s=Float64Set() for d in db: s.add(d) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(False if s.contains(i)==0 else True) return res def as_py_set_float64(Float64Set db): cdef Float64SetIterator it = db.get_iter() res=set() while it.has_next(): res.add(it.next()) return res ############# float32 - test from cykhash.khashsets cimport Float32Set, Float32SetIterator, isin_float32, Float32Set_from_buffer def py_isin_float32(query, db): s=Float32Set() for d in db: s.add(d) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(False if s.contains(i)==0 else True) return res def as_py_set_float32(Float32Set db): cdef Float32SetIterator it = db.get_iter() res=set() while it.has_next(): res.add(it.next()) return res ############# PyObject - test from cykhash.khashsets cimport PyObjectSet, PyObjectSetIterator, isin_pyobject, PyObjectSet_from_buffer def py_isin_pyobject(query, db): s=PyObjectSet() for d in db: s.add(d) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(False if s.contains(i)==0 else True) return res def as_py_set_pyobject(PyObjectSet db): cdef PyObjectSetIterator it = db.get_iter() res=set() while it.has_next(): res.add(it.next()) return res cykhash-2.0.0/tests/unit_tests/cymapinterfacetester.pyx000066400000000000000000000102031414255425400234730ustar00rootroot00000000000000 ############# int64 - test from cykhash.khashmaps cimport Int64toInt64Map, Int64toFloat64Map, Int64toInt64MapIterator, int64toint64_key_val_pair def use_int64(keys, values, query): s=Int64toInt64Map() for x,y in zip(keys, values): s.cput(x,y) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(s.cget(i)) return res def use_float64(keys, values, query): s=Int64toFloat64Map() for x,y in zip(keys, values): s.cput(x,y) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(s.cget(i)) return res def as_py_list_int64(Int64toInt64Map db): cdef Int64toInt64MapIterator it = db.get_iter(2) cdef int64toint64_key_val_pair p res=[] while it.has_next(): p = it.next() res+= [p.key, p.val] return res ############# int32 - test from cykhash.khashmaps cimport Int32toInt32Map, Int32toFloat32Map, Int32toInt32MapIterator, int32toint32_key_val_pair def use_int32(keys, values, query): s=Int32toInt32Map() for x,y in zip(keys, values): s.cput(x,y) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(s.cget(i)) return res def use_float32(keys, values, query): s=Int32toFloat32Map() for x,y in zip(keys, values): s.cput(x,y) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(s.cget(i)) return res def as_py_list_int32(Int32toInt32Map db): cdef Int32toInt32MapIterator it = db.get_iter(2) cdef int32toint32_key_val_pair p res=[] while it.has_next(): p = it.next() res+= [p.key, p.val] return res ############# float64 - test from cykhash.khashmaps cimport Float64toInt64Map, Float64toFloat64Map, Float64toInt64MapIterator, float64toint64_key_val_pair def use_int64_float64(keys, values, query): s=Float64toInt64Map() for x,y in zip(keys, values): s.cput(x,y) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(s.cget(i)) return res def use_float64_float64(keys, values, query): s=Float64toFloat64Map() for x,y in zip(keys, values): s.cput(x,y) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(s.cget(i)) return res def as_py_list_int64_float64(Float64toInt64Map db): cdef Float64toInt64MapIterator it = db.get_iter(2) cdef float64toint64_key_val_pair p res=[] while it.has_next(): p = it.next() res+= [int(p.key), p.val] return res ############# float32 - test from cykhash.khashmaps cimport Float32toInt32Map, Float32toFloat32Map, Float32toInt32MapIterator, float32toint32_key_val_pair def use_int32_float32(keys, values, query): s=Float32toInt32Map() for x,y in zip(keys, values): s.cput(x,y) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(s.cget(i)) return res def use_float32_float32(keys, values, query): s=Float32toFloat32Map() for x,y in zip(keys, values): s.cput(x,y) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(s.cget(i)) return res def as_py_list_int32_float32(Float32toInt32Map db): cdef Float32toInt32MapIterator it = db.get_iter(2) cdef float32toint32_key_val_pair p res=[] while it.has_next(): p = it.next() res+= [int(p.key), p.val] return res ############# float32 - test from cykhash.khashmaps cimport PyObjectMap, PyObjectMapIterator, pyobject_key_val_pair def use_pyobject(keys, values, query): s=PyObjectMap() for x,y in zip(keys, values): s.cput(x,y) assert s.size() == len(s) #to check size() exists res=[] for i in query: res.append(s.cget(i)) return res def as_py_list_pyobject(PyObjectMap db): cdef PyObjectMapIterator it = db.get_iter(2) cdef pyobject_key_val_pair p res=[] while it.has_next(): p = it.next() res+= [int((p.key)), (p.val)] return res cykhash-2.0.0/tests/unit_tests/test_CythonInterfaceMaps.py000066400000000000000000000042641414255425400240410ustar00rootroot00000000000000import pyximport; pyximport.install(setup_args = {"script_args" : ["--force"]}, language_level=3) import pytest from unittestmock import UnitTestMock import cymapinterfacetester as cyt from cykhash import Int64toInt64Map, Int32toInt32Map, Float64toInt64Map, Float32toInt32Map, PyObjectMap AS_LIST = {'int64' : cyt.as_py_list_int64, 'int32' : cyt.as_py_list_int32, 'float64' : cyt.as_py_list_int64_float64, 'float32' : cyt.as_py_list_int32_float32, 'object' : cyt.as_py_list_pyobject, } USE_INT = {'int64' : cyt.use_int64, 'int32' : cyt.use_int32, 'float64' : cyt.use_int64_float64, 'float32' : cyt.use_int32_float32, 'object' : cyt.use_pyobject, } USE_FLOAT = {'int64' : cyt.use_float64, 'int32' : cyt.use_float32, 'float64' : cyt.use_float64_float64, 'float32' : cyt.use_float32_float32, 'object' : cyt.use_pyobject, } MAP = {'int64' : Int64toInt64Map, 'int32' : Int32toInt32Map, 'float64' : Float64toInt64Map, 'float32' : Float32toInt32Map, 'object' : PyObjectMap, } #just making sure the interface can be accessed: @pytest.mark.parametrize( "map_type", [ 'int64', 'int32', 'float64', 'float32', ]) class TestCyMapInterface(UnitTestMock): def test_cimport_use_int(self, map_type): received=USE_INT[map_type]([1,2,3,4], [5,6,7,8], [2,3]) expected=[6,7] self.assertEqual(received, expected) def test_cimport_use_float(self, map_type): received=USE_FLOAT[map_type]([1,2,3,4], [5.5,6.5,7.5,8.5], [2,3]) expected=[6.5,7.5] self.assertEqual(received, expected) def test_as_py_list(self, map_type): cy_map = MAP[map_type]() cy_map[3] = 20 lst = AS_LIST[map_type](cy_map) self.assertEqual(lst, [3,20]) def test_as_py_list_2(self, map_type): cy_map = MAP[map_type]() cy_map[3] = 5 cy_map[4] = 6 lst = AS_LIST[map_type](cy_map) self.assertEqual(set(lst), set([3,4,5,6])) cykhash-2.0.0/tests/unit_tests/test_CythonInterfaceSets.py000066400000000000000000000051351414255425400240550ustar00rootroot00000000000000import pyximport; pyximport.install(setup_args = {"script_args" : ["--force"]}, language_level=3) from unittestmock import UnitTestMock import cyinterfacetester as cyt from cykhash import Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet #just making sure the interface can be accessed: class TestCyIntegerface(UnitTestMock): def test_cimport_works_i64(self): received=cyt.py_isin_int64([1,2,3,4], [2,4]) expected=[False, True, False, True] self.assertEqual(received, expected) def test_iter_interface_works_i64(self): cy_set = Int64Set() py_set = set() for i in range(10): cy_set.add(i) py_set.add(i) clone = cyt.as_py_set_int64(cy_set) self.assertEqual(py_set, clone) ### ------------------------------- def test_cimport_works_i32(self): received=cyt.py_isin_int32([1,2,3,4], [2,4]) expected=[False, True, False, True] self.assertEqual(received, expected) def test_iter_interface_works_i32(self): cy_set = Int32Set() py_set = set() for i in range(10): cy_set.add(i) py_set.add(i) clone = cyt.as_py_set_int32(cy_set) self.assertEqual(py_set, clone) ### ------------------------------- def test_cimport_works_f64(self): received=cyt.py_isin_float64([1,2,3,4], [2,4]) expected=[False, True, False, True] self.assertEqual(received, expected) def test_iter_interface_works_f64(self): cy_set = Float64Set() py_set = set() for i in range(10): cy_set.add(i) py_set.add(i) clone = cyt.as_py_set_float64(cy_set) self.assertEqual(py_set, clone) ### ------------------------------- def test_cimport_works_f32(self): received=cyt.py_isin_float32([1,2,3,4], [2,4]) expected=[False, True, False, True] self.assertEqual(received, expected) def test_iter_interface_works_f32(self): cy_set = Float32Set() py_set = set() for i in range(10): cy_set.add(i) py_set.add(i) clone = cyt.as_py_set_float32(cy_set) self.assertEqual(py_set, clone) ### ------------------------------- def test_cimport_works_pyobject(self): received=cyt.py_isin_pyobject([1,2,3,4], [2,4]) expected=[False, True, False, True] self.assertEqual(received, expected) def test_iter_interface_works_pyobject(self): cy_set = PyObjectSet() py_set = set() for i in range(10): cy_set.add(i) py_set.add(i) clone = cyt.as_py_set_pyobject(cy_set) self.assertEqual(py_set, clone) cykhash-2.0.0/tests/unit_tests/test_MapIterator.py000066400000000000000000000036621414255425400223630ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock from cykhash import Int64toInt64Map, Int32toInt32Map, Float64toInt64Map, Float32toInt32Map, PyObjectMap from cykhash import Int64toFloat64Map, Int32toFloat32Map, Float64toFloat64Map, Float32toFloat32Map @pytest.mark.parametrize( "map_type", [ Int64toInt64Map, Int32toInt32Map, Float64toInt64Map, Float32toInt32Map, Int64toFloat64Map, Int32toFloat32Map, Float64toFloat64Map, Float32toFloat32Map, PyObjectMap] ) class TestMapIterator(UnitTestMock): def test_iterate(self, map_type): cy_map = map_type() py_map = dict() for i in range(10): cy_map[i] = i+1 py_map[i] = i+1 clone = dict() for x in cy_map.items(): clone[x[0]] = x[1] self.assertEqual(py_map, clone) def test_iterate_after_clear(self, map_type): cy_map = map_type(zip(range(1000), range(1000))) it = iter(cy_map.items()) for x in range(1000): next(it) cy_map.clear() with pytest.raises(StopIteration): next(it) def test_iterate_after_growing(self, map_type): cy_map = map_type() it = iter(cy_map.keys()) #change bucket size for i in range(1000): cy_map[i]=i #make sure new size is used lst = [] with pytest.raises(StopIteration): for x in range(1001): lst.append(next(it)) self.assertEqual(set(lst), set(range(1000))) def test_iterate_after_growing2(self, map_type): cy_map = map_type() cy_map[42]=42 it = iter(cy_map.keys()) next(it) #iterator shows to end now cy_map[11]=11 cy_map[15]=15 cy_map[13]=13 #old end is no longer an end try: self.assertTrue(next(it) in {42,11,15,13}) except StopIteration: pass # stop iteration could be an acceptable outcome cykhash-2.0.0/tests/unit_tests/test_Maps.py000066400000000000000000000133471414255425400210350ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock import struct from cykhash import Int64toInt64Map, Int32toInt32Map, Float64toInt64Map, Float32toInt32Map, PyObjectMap from cykhash import Int64toFloat64Map, Int32toFloat32Map, Float64toFloat64Map, Float32toFloat32Map @pytest.mark.parametrize( "map_type", [ Int64toInt64Map, Int32toInt32Map, Float64toInt64Map, Float32toInt32Map, Int64toFloat64Map, Int32toFloat32Map, Float64toFloat64Map, Float32toFloat32Map, ], ) class TestCommonMap(UnitTestMock): def test_created_empty(self, map_type): s=map_type() self.assertEqual(len(s), 0) def test_put_int_once(self, map_type): s=map_type() s[1] = 43 self.assertEqual(len(s), 1) self.assertEqual(s[1], 43) def test_put_int_twice(self, map_type): s=map_type() s[1] = 43 s[1] = 43 self.assertEqual(len(s), 1) self.assertEqual(s[1], 43) def test_add_two(self, map_type): s=map_type() s[1] = 43 s[2] = 44 self.assertEqual(len(s), 2) self.assertEqual(s[1], 43) self.assertEqual(s[2], 44) def test_add_many_twice(self, map_type): N=1000 s=map_type() for i in range(N): s[i] = 44 self.assertEqual(len(s), i+1) #no changes for the second insert: for i in range(N): s[i] = 44 self.assertEqual(len(s), N) def test_contains_none(self, map_type): N=1000 s=map_type() for i in range(N): self.assertFalse(i in s) def test_contains_all(self, map_type): N=1000 s=map_type() for i in range(N): s[i] = i+1 for i in range(N): self.assertTrue(i in s) def test_contains_odd(self, map_type): N=1000 s=map_type() for i in range(N): if i%2==1: s[i] = i+1 for i in range(N): self.assertEqual(i in s, i%2==1) def test_contains_even(self, map_type): N=1000 s=map_type() for i in range(N): if i%2==0: s[i] = i+1 for i in range(N): self.assertEqual(i in s, i%2==0) def test_delete_even(self, map_type): N=1000 s=map_type() for i in range(N): s[i] = i+1 #delete even: for i in range(N): if i%2==0: n=len(s) s.discard(i) self.assertEqual(len(s), n-1) s.discard(i) self.assertEqual(len(s), n-1) #check odd is still inside: for i in range(N): self.assertEqual(i in s, i%2==1) def test_negative_hint(self, map_type): with pytest.raises(OverflowError) as context: map_type(number_of_elements_hint=-1) self.assertEqual("can't convert negative value to uint32_t", str(context.value)) def test_no_such_key(self, map_type): with pytest.raises(KeyError) as context: map_type(number_of_elements_hint=100)[55] self.assertEqual(context.value.args[0], 55) def test_zero_hint_ok(self, map_type): s = map_type(number_of_elements_hint=0) s[4] = 7 s[5] = 7 self.assertTrue(4 in s) self.assertTrue(5 in s) def test_as_put_get_int(self, map_type): s = map_type(number_of_elements_hint=20) s[4] = 5 self.assertEqual(s[4], 5) def test_cput_cget_int(self, map_type): s = map_type() s.cput(1, 43) self.assertEqual(len(s), 1) self.assertEqual(s.cget(1), 43) ###### special testers @pytest.mark.parametrize( "map_type", [ Float64toFloat64Map, Float32toFloat32Map, Int64toFloat64Map, Int32toFloat32Map, PyObjectMap ], ) class TestFloatVal(UnitTestMock): def test_as_put_get_float(self, map_type): s = map_type(number_of_elements_hint=20) s[4] = 5.4 self.assertTrue(abs(s[4]-5.4)<1e-5) @pytest.mark.parametrize( "map_type", [ Float64toFloat64Map, Float32toFloat32Map, Float64toInt64Map, Float32toInt32Map, PyObjectMap ], ) class TestFloat(UnitTestMock): def test_nan_right(self, map_type): NAN=float("nan") s=map_type() self.assertFalse(NAN in s) s[NAN] = 1 self.assertTrue(NAN in s) def test_all_nans_the_same(self, map_type): NAN1=struct.unpack("d", struct.pack("=Q", 9221120237041090560))[0] NAN2=struct.unpack("d", struct.pack("=Q", 9221120237061090562))[0] NAN3=struct.unpack("d", struct.pack("=Q", 9221120237042090562))[0] s=map_type() s[NAN1]=1 s[NAN2]=1 s[NAN3]=1 for nan_id in range(9221120237041090560, 9221120237061090562, 1111): nan = struct.unpack("d", struct.pack("=Q", nan_id))[0] s[nan] = 1 self.assertEqual(len(s), 1) #+0.0/-0.0 will break when there are more than 2**32 elements in the map # bacause then hash-function will put them in different buckets def test_signed_zero1(self, map_type): MINUS_ZERO=float("-0.0") PLUS_ZERO =float("0.0") self.assertFalse(str(MINUS_ZERO)==str(PLUS_ZERO)) s=map_type() for i in range(1,2000): s[i] = i self.assertFalse(MINUS_ZERO in s) self.assertFalse(PLUS_ZERO in s) s[MINUS_ZERO]=10 self.assertTrue(MINUS_ZERO in s) self.assertTrue(PLUS_ZERO in s) def test_signed_zero2(self, map_type): MINUS_ZERO=float("-0.0") PLUS_ZERO =float("0.0") s=map_type() for i in range(1,2000): s[i] = i self.assertFalse(MINUS_ZERO in s) self.assertFalse(PLUS_ZERO in s) s[PLUS_ZERO] = 12 self.assertTrue(MINUS_ZERO in s) self.assertTrue(PLUS_ZERO in s) def test_all_nans_the_same_float32(): s=Float32toInt32Map() for nan_id in range(2143299343, 2143499343): nan = struct.unpack("f", struct.pack("=I", nan_id))[0] s[nan] = 1 assert len(s) == 1 cykhash-2.0.0/tests/unit_tests/test_Maps_bwc.py000066400000000000000000000101631414255425400216610ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock import struct from cykhash import Int64to64Map, Int32to32Map, Float64to64Map, Float32to32Map, PyObjectMap @pytest.mark.parametrize( "map_type", [ Int64to64Map, Int32to32Map, Float64to64Map, Float32to32Map, ], ) class TestCommonMap(UnitTestMock): def test_created_empty(self, map_type): s=map_type() self.assertEqual(len(s), 0) def test_put_int_once(self, map_type): s=map_type() s[1] = 43 self.assertEqual(len(s), 1) self.assertEqual(s[1], 43) def test_put_int_twice(self, map_type): s=map_type() s[1] = 43 s[1] = 43 self.assertEqual(len(s), 1) self.assertEqual(s[1], 43) def test_add_two(self, map_type): s=map_type() s[1] = 43 s[2] = 44 self.assertEqual(len(s), 2) self.assertEqual(s[1], 43) self.assertEqual(s[2], 44) def test_add_many_twice(self, map_type): N=1000 s=map_type() for i in range(N): s[i] = 44 self.assertEqual(len(s), i+1) #no changes for the second insert: for i in range(N): s[i] = 44 self.assertEqual(len(s), N) def test_contains_none(self, map_type): N=1000 s=map_type() for i in range(N): self.assertFalse(i in s) def test_contains_all(self, map_type): N=1000 s=map_type() for i in range(N): s[i] = i+1 for i in range(N): self.assertTrue(i in s) def test_contains_odd(self, map_type): N=1000 s=map_type() for i in range(N): if i%2==1: s[i] = i+1 for i in range(N): self.assertEqual(i in s, i%2==1) def test_contains_even(self, map_type): N=1000 s=map_type() for i in range(N): if i%2==0: s[i] = i+1 for i in range(N): self.assertEqual(i in s, i%2==0) def test_delete_even(self, map_type): N=1000 s=map_type() for i in range(N): s[i] = i+1 #delete even: for i in range(N): if i%2==0: n=len(s) s.discard(i) self.assertEqual(len(s), n-1) s.discard(i) self.assertEqual(len(s), n-1) #check odd is still inside: for i in range(N): self.assertEqual(i in s, i%2==1) def test_zero_hint_ok(self, map_type): s = map_type(number_of_elements_hint=0) s[4] = 7 s[5] = 7 self.assertTrue(4 in s) self.assertTrue(5 in s) def test_as_int64_a_float(self, map_type): s = map_type(number_of_elements_hint=20, for_int=True) s[4] = 5.4 self.assertEqual(s[4], 5) def test_as_int64_put_get(self, map_type): s = map_type(number_of_elements_hint=20, for_int=True) s[4] = 5 self.assertEqual(s[4], 5) def test_as_float64_put_get(self, map_type): s = map_type(number_of_elements_hint=20, for_int=False) s[4] = 5 self.assertEqual(s[4], 5) @pytest.mark.parametrize( "map_type", [ Float64to64Map, Float32to32Map, PyObjectMap, ], ) class TestFloat(UnitTestMock): def test_nan_right(self, map_type): NAN=float("nan") s=map_type() self.assertFalse(NAN in s) s[NAN] = 1 self.assertTrue(NAN in s) #+0.0/-0.0 will break when there are more than 2**32 elements in the map # bacause then hash-function will put them in different buckets def test_signed_zero1(self, map_type): MINUS_ZERO=float("-0.0") PLUS_ZERO =float("0.0") self.assertFalse(str(MINUS_ZERO)==str(PLUS_ZERO)) s=map_type() for i in range(1,2000): s[i] = i self.assertFalse(MINUS_ZERO in s) self.assertFalse(PLUS_ZERO in s) s[MINUS_ZERO]=10 self.assertTrue(MINUS_ZERO in s) self.assertTrue(PLUS_ZERO in s) def test_signed_zero2(self, map_type): MINUS_ZERO=float("-0.0") PLUS_ZERO =float("0.0") s=map_type() for i in range(1,2000): s[i] = i self.assertFalse(MINUS_ZERO in s) self.assertFalse(PLUS_ZERO in s) s[PLUS_ZERO] = 12 self.assertTrue(MINUS_ZERO in s) self.assertTrue(PLUS_ZERO in s) cykhash-2.0.0/tests/unit_tests/test_PyObjectMapExtras.py000066400000000000000000000132321414255425400234720ustar00rootroot00000000000000import sys from unittestmock import UnitTestMock from cykhash.compat import PYPY, assert_if_not_on_PYPY import pytest not_on_pypy = pytest.mark.skipif(PYPY, reason="pypy has no refcounting") from cykhash import PyObjectMap class TestPyObjectMapMisc(UnitTestMock): def test_from_keys_works(self): s=PyObjectMap.fromkeys(["a", "b", "c"], "kkk") self.assertEqual(len(s), 3) self.assertEqual(s["a"],"kkk") @not_on_pypy class TestRefCounterPyObjectMap(UnitTestMock): def test_map_put_discard_right_refcnts(self): a=4200 s=PyObjectMap() old_ref_cnt = sys.getrefcount(a) s[a] = a self.assertEqual(sys.getrefcount(a), old_ref_cnt+2, msg="first add") s[a] = a # shouldn't do anything self.assertEqual(sys.getrefcount(a), old_ref_cnt+2, msg="second add") s.discard(a) self.assertEqual(sys.getrefcount(a), old_ref_cnt, msg="discard") def test_map_deallocate_decrefs(self): a=4200 s=PyObjectMap() old_ref_cnt = sys.getrefcount(a) s[a] = a self.assertEqual(sys.getrefcount(a), old_ref_cnt+2) del s self.assertEqual(sys.getrefcount(a), old_ref_cnt) def test_map_interator_right_refcnts(self): a=4200 s=PyObjectMap() old_ref_cnt = sys.getrefcount(a) s[a] = a self.assertEqual(sys.getrefcount(a), old_ref_cnt+2) lst = list(s.items()) # now also lst helds two additional references self.assertEqual(sys.getrefcount(a), old_ref_cnt+4) def test_first_object_kept(self): a,b =float("nan"), float("nan") self.assertTrue(a is not b) #prerequisite s=PyObjectMap() s[a] = a s[b] = b lst = list(s.items()) self.assertEqual(len(lst), 1) self.assertTrue(a is lst[0][0]) def test_discard_with_equivalent_object(self): a,b =float("nan"), float("nan") self.assertTrue(a is not b) #prerequisite old_ref_cnt_a = sys.getrefcount(a) old_ref_cnt_b = sys.getrefcount(b) s=PyObjectMap() s[a] = None s[b] = None self.assertEqual(sys.getrefcount(a), old_ref_cnt_a+1) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) self.assertTrue(b in s) self.assertTrue(a in s) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a+1) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s.discard(b) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s.discard(b) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s.discard(a) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s[b] = None self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b+1) def test_rewrite_works(self): a,b =float("nan"), float("nan") self.assertTrue(a is not b) #prerequisite old_ref_cnt_a = sys.getrefcount(a) old_ref_cnt_b = sys.getrefcount(b) s=PyObjectMap() s[a] = a self.assertEqual(sys.getrefcount(a), old_ref_cnt_a+2) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s[b] = b self.assertEqual(sys.getrefcount(a), old_ref_cnt_a+1) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b+1) del s self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) def test_rewrite_works(self): a,b =float("nan"), float("nan") self.assertTrue(a is not b) #prerequisite old_ref_cnt_a = sys.getrefcount(a) old_ref_cnt_b = sys.getrefcount(b) s=PyObjectMap() s[a] = a self.assertEqual(sys.getrefcount(a), old_ref_cnt_a+2) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s[b] = b self.assertEqual(sys.getrefcount(a), old_ref_cnt_a+1) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b+1) del s self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) def test_nan_float(): nan1 = float("nan") nan2 = float("nan") assert_if_not_on_PYPY(nan1 is not nan2, reason="nan is singelton in PyPy") table = PyObjectMap() table[nan1] = 42 assert table[nan2] == 42 def test_nan_complex(): nan1 = complex(0, float("nan")) nan2 = complex(0, float("nan")) assert_if_not_on_PYPY(nan1 is not nan2, reason="nan is singelton in PyPy") table = PyObjectMap() table[nan1] = 42 assert table[nan2] == 42 def test_nan_in_tuple(): nan1 = (float("nan"),) nan2 = (float("nan"),) assert_if_not_on_PYPY(nan1[0] is not nan2[0], reason="nan is singelton in PyPy") table = PyObjectMap() table[nan1] = 42 assert table[nan2] == 42 def test_nan_in_nested_tuple(): nan1 = (1, (2, (float("nan"),))) nan2 = (1, (2, (float("nan"),))) other = (1, 2) table = PyObjectMap() table[nan1] = 42 assert table[nan2] == 42 assert other not in table def test_unique_for_nan_objects_floats(): table = PyObjectMap(zip([float("nan") for i in range(50)], range(50))) assert len(table) == 1 def test_unique_for_nan_objects_complex(): table = PyObjectMap(zip([complex(float("nan"), 1.0) for i in range(50)], range(50))) assert len(table) == 1 def test_unique_for_nan_objects_tuple(): table = PyObjectMap(zip([(1.0, (float("nan"), 1.0)) for i in range(50)], range(50))) assert len(table) == 1 def test_float_complex_int_are_equal_as_objects(): table = PyObjectMap(zip(range(129), range(129))) assert table[5] == 5 assert table[5.0] == 5 assert table[5.0+0j] == 5 cykhash-2.0.0/tests/unit_tests/test_PyObjectSetExtras.py000066400000000000000000000077711414255425400235230ustar00rootroot00000000000000import sys from unittestmock import UnitTestMock from cykhash.compat import PYPY, assert_if_not_on_PYPY import pytest not_on_pypy = pytest.mark.skipif(PYPY, reason="pypy has no refcounting") from cykhash import PyObjectSet @not_on_pypy class TestRefCounter(UnitTestMock): def test_set_add_discard_right_refcnts(self): a=4200 s=PyObjectSet() old_ref_cnt = sys.getrefcount(a) s.add(a) self.assertEqual(sys.getrefcount(a), old_ref_cnt+1, msg="first add") s.add(a) # shouldn't do anything self.assertEqual(sys.getrefcount(a), old_ref_cnt+1, msg="second add") s.discard(a) self.assertEqual(sys.getrefcount(a), old_ref_cnt, msg="discard") def test_set_deallocate_decrefs(self): a=4200 s=PyObjectSet() old_ref_cnt = sys.getrefcount(a) s.add(a) self.assertEqual(sys.getrefcount(a), old_ref_cnt+1) del s self.assertEqual(sys.getrefcount(a), old_ref_cnt) def test_set_interator_right_refcnts(self): a=4200 s=PyObjectSet() old_ref_cnt = sys.getrefcount(a) s.add(a) self.assertEqual(sys.getrefcount(a), old_ref_cnt+1) lst = list(s) # now also lst helds a additional reference self.assertEqual(sys.getrefcount(a), old_ref_cnt+2) def test_first_object_kept(self): a,b =float("nan"), float("nan") self.assertTrue(a is not b) #prerequisite s=PyObjectSet() s.add(a) s.add(b) lst = list(s) self.assertEqual(len(lst), 1) self.assertTrue(a is lst[0]) def test_discard_with_equivalent_object(self): a,b =float("nan"), float("nan") self.assertTrue(a is not b) #prerequisite old_ref_cnt_a = sys.getrefcount(a) old_ref_cnt_b = sys.getrefcount(b) s=PyObjectSet() s.add(a) s.add(b) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a+1) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) self.assertTrue(b in s) self.assertTrue(a in s) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a+1) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s.discard(b) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s.discard(b) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s.discard(a) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b) s.add(b) self.assertEqual(sys.getrefcount(a), old_ref_cnt_a) self.assertEqual(sys.getrefcount(b), old_ref_cnt_b+1) def test_nan_float(): nan1 = float("nan") nan2 = float("nan") assert_if_not_on_PYPY(nan1 is not nan2, reason="nan is singelton in PyPy") s = PyObjectSet() s.add(nan1) assert nan2 in s def test_nan_complex(): nan1 = complex(0, float("nan")) nan2 = complex(0, float("nan")) assert_if_not_on_PYPY(nan1 is not nan2, reason="nan is singelton in PyPy") s = PyObjectSet() s.add(nan1) assert nan2 in s def test_nan_in_tuple(): nan1 = (float("nan"),) nan2 = (float("nan"),) assert_if_not_on_PYPY(nan1[0] is not nan2[0], reason="nan is singelton in PyPy") s = PyObjectSet() s.add(nan1) assert nan2 in s def test_nan_in_nested_tuple(): nan1 = (1, (2, (float("nan"),))) nan2 = (1, (2, (float("nan"),))) other = (1, 2) s = PyObjectSet() s.add(nan1) assert nan2 in s assert other not in s def test_unique_for_nan_objects_floats(): s = PyObjectSet([float("nan") for i in range(50)]) assert len(s) == 1 def test_unique_for_nan_objects_complex(): s = PyObjectSet([complex(float("nan"), 1.0) for i in range(50)]) assert len(s) == 1 def test_unique_for_nan_objects_tuple(): s = PyObjectSet([(1.0, (float("nan"), 1.0)) for i in range(50)]) assert len(s) == 1 def test_float_complex_int_are_equal_as_objects(): s = PyObjectSet(range(129)) assert 5 in s assert 5.0 in s assert 5.0+0j in s cykhash-2.0.0/tests/unit_tests/test_SetIterator.py000066400000000000000000000031711414255425400223740ustar00rootroot00000000000000from unittestmock import UnitTestMock import pytest from cykhash import Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestSetIterator(UnitTestMock): def test_iterate(self, set_type): cy_set = set_type() py_set = set() for i in range(10): cy_set.add(i) py_set.add(i) clone = set() for x in cy_set: clone.add(x) self.assertEqual(py_set, clone) def test_iterate_after_clear(self, set_type): cy_set = set_type(range(1000)) it = iter(cy_set) for x in range(1000): next(it) cy_set.clear() with pytest.raises(StopIteration): next(it) def test_iterate_after_growing(self, set_type): cy_set = set_type() it = iter(cy_set) #change bucket size for i in range(1000): cy_set.add(i) #make sure new size is used lst = [] with pytest.raises(StopIteration): for x in range(1001): lst.append(next(it)) self.assertEqual(set(lst), set(range(1000))) def test_iterate_after_growing2(self, set_type): cy_set = set_type() cy_set.add(42) it = iter(cy_set) next(it) #iterator shows to end now cy_set.add(11) cy_set.add(15) cy_set.add(13) #old end is no longer an end try: self.assertTrue(next(it) in {42,11,15,13}) except StopIteration: pass # stop iteration could be an acceptable outcome cykhash-2.0.0/tests/unit_tests/test_Sets.py000066400000000000000000000117731414255425400210540ustar00rootroot00000000000000from unittestmock import UnitTestMock import pytest import struct from cykhash import Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestSet(UnitTestMock): def test_created_empty(self, set_type): s=set_type() self.assertEqual(len(s), 0) def test_add_once(self, set_type): s=set_type() s.add(1) self.assertEqual(len(s), 1) def test_add_twice(self, set_type): s=set_type() s.add(1) s.add(1) self.assertEqual(len(s), 1) def test_add_two(self, set_type): s=set_type() s.add(1) s.add(2) self.assertEqual(len(s), 2) def test_add_many_twice(self, set_type): N=1000 s=set_type() for i in range(N): s.add(i) self.assertEqual(len(s), i+1) #no changes for the second insert: for i in range(N): s.add(i) self.assertEqual(len(s), N) def test_contains_none(self, set_type): N=1000 s=set_type() for i in range(N): self.assertFalse(i in s) def test_contains_all(self, set_type): N=1000 s=set_type() for i in range(N): s.add(i) for i in range(N): self.assertTrue(i in s) def test_contains_odd(self, set_type): N=1000 s=set_type() for i in range(N): if i%2==1: s.add(i) for i in range(N): self.assertEqual(i in s, i%2==1) def test_contains_even(self, set_type): N=1000 s=set_type() for i in range(N): if i%2==0: s.add(i) for i in range(N): self.assertEqual(i in s, i%2==0) def test_delete_even(self, set_type): N=1000 s=set_type() for i in range(N): s.add(i) #delete even: for i in range(N): if i%2==0: n=len(s) s.discard(i) self.assertEqual(len(s), n-1) s.discard(i) self.assertEqual(len(s), n-1) #check odd is still inside: for i in range(N): self.assertEqual(i in s, i%2==1) def test_negative_hint(self, set_type): with pytest.raises(OverflowError) as context: set_type(number_of_elements_hint=-1) self.assertEqual("can't convert negative value to uint32_t", context.value.args[0]) def test_zero_hint_ok(self, set_type): s = set_type(number_of_elements_hint=0) s.add(4) s.add(5) self.assertTrue(4 in s) self.assertTrue(5 in s) def test_get_state_info(self, set_type): s = set_type(number_of_elements_hint=100) info = s.get_state_info() self.assertTrue(info['n_buckets'] == 256) self.assertTrue(info['n_occupied'] == 0) self.assertTrue("upper_bound" in info) @pytest.mark.parametrize( "set_type", [Float64Set, Float32Set, PyObjectSet] ) class TestFloatSet(UnitTestMock): def test_nan_right(self, set_type): NAN=float("nan") s=set_type() self.assertFalse(NAN in s) s.add(NAN) self.assertTrue(NAN in s) def test_all_nans_the_same(self, set_type): NAN1=struct.unpack("d", struct.pack("=Q", 9221120237041090560))[0] NAN2=struct.unpack("d", struct.pack("=Q", 9221120237061090562))[0] NAN3=struct.unpack("d", struct.pack("=Q", 9221120237042090562))[0] s=set_type() s.add(NAN1) s.add(NAN2) s.add(NAN3) for nan_id in range(9221120237041090560, 9221120237061090562, 1111): nan = struct.unpack("d", struct.pack("=Q", nan_id))[0] s.add(nan) self.assertEqual(len(s), 1) for nan_id in range(9221120237041090560, 9221120237061090562, 1111): nan = struct.unpack("d", struct.pack("=Q", nan_id))[0] self.assertTrue(nan in s) #+0.0/-0.0 will break when there are more than 2**32 elements in the map # bacause then hash-function will put them in different buckets def test_signed_zero1(self, set_type): MINUS_ZERO=float("-0.0") PLUS_ZERO =float("0.0") self.assertFalse(str(MINUS_ZERO)==str(PLUS_ZERO)) s=set_type() for i in range(1,2000): s.add(i) self.assertFalse(MINUS_ZERO in s) self.assertFalse(PLUS_ZERO in s) s.add(MINUS_ZERO) self.assertTrue(MINUS_ZERO in s) self.assertTrue(PLUS_ZERO in s) def test_signed_zero2(self, set_type): MINUS_ZERO=float("-0.0") PLUS_ZERO =float("0.0") s=set_type() for i in range(1,2000): s.add(i) self.assertFalse(MINUS_ZERO in s) self.assertFalse(PLUS_ZERO in s) s.add(PLUS_ZERO) self.assertTrue(MINUS_ZERO in s) self.assertTrue(PLUS_ZERO in s) def test_all_nans_the_same_float32(): s=Float32Set() for nan_id in range(2143299343, 2143499343): nan = struct.unpack("f", struct.pack("=I", nan_id))[0] s.add(nan) assert len(s) == 1 for nan_id in range(2143299343, 2143499343): nan = struct.unpack("f", struct.pack("=I", nan_id))[0] assert nan in s cykhash-2.0.0/tests/unit_tests/test_all.py000066400000000000000000000132721414255425400207020ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock import numpy as np from cykhash import all_int64, all_int64_from_iter, Int64Set_from, Int64Set_from_buffer from cykhash import all_int32, all_int32_from_iter, Int32Set_from, Int32Set_from_buffer from cykhash import all_float64, all_float64_from_iter, Float64Set_from, Float64Set_from_buffer from cykhash import all_float32, all_float32_from_iter, Float32Set_from, Float32Set_from_buffer from cykhash import all_pyobject, all_pyobject_from_iter, PyObjectSet_from, PyObjectSet_from_buffer ALL={'int32': all_int32, 'int64': all_int64, 'float64' : all_float64, 'float32' : all_float32} ALL_FROM_ITER={'int32': all_int32_from_iter, 'int64': all_int64_from_iter, 'float64' : all_float64_from_iter, 'float32' : all_float32_from_iter} FROM_SET={'int32': Int32Set_from, 'int64': Int64Set_from, 'float64' : Float64Set_from, 'float32' : Float32Set_from, 'pyobject' : PyObjectSet_from} BUFFER_SIZE = {'int32': 'i', 'int64': 'q', 'float64' : 'd', 'float32' : 'f'} import array @pytest.mark.parametrize( "value_type", ['int64', 'int32', 'float64', 'float32'] ) class TestAll(UnitTestMock): def test_all_yes(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], [2,4,6]*6) result=ALL[value_type](a,s) self.assertEqual(result, True) def test_all_yes_from_iter(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=[2,4,6]*6 result=ALL_FROM_ITER[value_type](a,s) self.assertEqual(result, True) def test_all_last_no(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], [2]*6+[3]) result=ALL[value_type](a,s) self.assertEqual(result, False) def test_all_last_no_from_iter(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=[2]*6+[3] result=ALL_FROM_ITER[value_type](a,s) self.assertEqual(result, False) def test_all_empty(self, value_type): s=FROM_SET[value_type]([]) a=array.array(BUFFER_SIZE[value_type],[]) result=ALL[value_type](a,s) self.assertEqual(result, True) def test_all_empty_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=[] result=ALL_FROM_ITER[value_type](a,s) self.assertEqual(result, True) def test_all_empty_set(self, value_type): s=FROM_SET[value_type]([]) a=array.array(BUFFER_SIZE[value_type],[1]) result=ALL[value_type](a,s) self.assertEqual(result, False) def test_all_empty_set_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=[1] result=ALL_FROM_ITER[value_type](a,s) self.assertEqual(result, False) def test_noniter_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=1 with pytest.raises(TypeError) as context: ALL_FROM_ITER[value_type](a,s) self.assertTrue("object is not iterable" in str(context.value)) def test_memview_none(self, value_type): s=FROM_SET[value_type]([]) self.assertEqual(ALL[value_type](None,s), True) def test_dbnone(self, value_type): a=array.array(BUFFER_SIZE[value_type],[1]) self.assertEqual(ALL[value_type](a,None), False) def test_dbnone_empty_query(self, value_type): a=array.array(BUFFER_SIZE[value_type],[]) self.assertEqual(ALL[value_type](a,None), True) def test_dbnone_from_iter(self, value_type): a=[1] self.assertEqual(ALL_FROM_ITER[value_type](a,None), False) def test_dbnone_empty_query_from_iter(self, value_type): self.assertEqual(ALL_FROM_ITER[value_type]([],None), True) class TestAllPyObject(UnitTestMock): def test_all_yes(self): s=PyObjectSet_from([2,4,666]) a=np.array([2,4,666]*6, dtype=np.object) result=all_pyobject(a,s) self.assertEqual(result, True) def test_all_from_iter(self): s=PyObjectSet_from([2,4,666]) a=[2,4,666]*6 result=all_pyobject_from_iter(a,s) self.assertEqual(result, True) def test_all_last_no(self): s=PyObjectSet_from([2,4,666]) a=np.array([2,4,666]*6+[3], dtype=np.object) result=all_pyobject(a,s) self.assertEqual(result, False) def test_all_last_no_from_iter(self): s=PyObjectSet_from([2,4,666]) a=[2,4,666]*6+[3] result=all_pyobject_from_iter(a,s) self.assertEqual(result, False) def test_all_empty(self): s=PyObjectSet_from([]) a=np.array([], dtype=np.object) result=all_pyobject(a,s) self.assertEqual(result, True) def test_all_empty_from_iter(self): s=PyObjectSet_from([]) a=[] result=all_pyobject_from_iter(a,s) self.assertEqual(result, True) def test_all_empty_set(self): s=PyObjectSet_from([]) a=np.array([1], dtype=np.object) result=all_pyobject(a,s) self.assertEqual(result, False) def test_all_empty_set_from_iter(self): s=PyObjectSet_from([]) a=[1] result=all_pyobject_from_iter(a,s) self.assertEqual(result, False) def test_noniter_from_iter(self): s=PyObjectSet_from([]) a=1 with pytest.raises(TypeError) as context: all_pyobject_from_iter(a,s) self.assertTrue("object is not iterable" in str(context.value)) def test_memview_none(self): s=PyObjectSet_from([]) self.assertEqual(all_pyobject(None,s), True) def test_dbnone(self): a=np.array([1], dtype=np.object) self.assertEqual(all_pyobject(a,None), False) def test_dbnone_from_iter(self): a=[1] self.assertEqual(all_pyobject_from_iter(a,None), False) cykhash-2.0.0/tests/unit_tests/test_any.py000066400000000000000000000126411414255425400207200ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock import numpy as np from cykhash import any_int64, any_int64_from_iter, Int64Set_from, Int64Set_from_buffer from cykhash import any_int32, any_int32_from_iter, Int32Set_from, Int32Set_from_buffer from cykhash import any_float64, any_float64_from_iter, Float64Set_from, Float64Set_from_buffer from cykhash import any_float32, any_float32_from_iter, Float32Set_from, Float32Set_from_buffer from cykhash import any_pyobject, any_pyobject_from_iter, PyObjectSet_from, PyObjectSet_from_buffer ANY={'int32': any_int32, 'int64': any_int64, 'float64' : any_float64, 'float32' : any_float32} ANY_FROM_ITER={'int32': any_int32_from_iter, 'int64': any_int64_from_iter, 'float64' : any_float64_from_iter, 'float32' : any_float32_from_iter} FROM_SET={'int32': Int32Set_from, 'int64': Int64Set_from, 'float64' : Float64Set_from, 'float32' : Float32Set_from, 'pyobject' : PyObjectSet_from} BUFFER_SIZE = {'int32': 'i', 'int64': 'q', 'float64' : 'd', 'float32' : 'f'} import array @pytest.mark.parametrize( "value_type", ['int64', 'int32', 'float64', 'float32'] ) class TestAny(UnitTestMock): def test_any_no(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], [1,3,5]*6) result=ANY[value_type](a,s) self.assertEqual(result, False) def test_any_no_from_iter(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=[1,3,5]*6 result=ANY_FROM_ITER[value_type](a,s) self.assertEqual(result, False) def test_any_last_yes(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], [1]*6+[2]) result=ANY[value_type](a,s) self.assertEqual(result, True) def test_any_last_yes_from_iter(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=[1]*6+[2] result=ANY_FROM_ITER[value_type](a,s) self.assertEqual(result, True) def test_any_empty(self, value_type): s=FROM_SET[value_type]([]) a=array.array(BUFFER_SIZE[value_type],[]) result=ANY[value_type](a,s) self.assertEqual(result, False) def test_any_empty_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=[] result=ANY_FROM_ITER[value_type](a,s) self.assertEqual(result, False) def test_any_empty_set(self, value_type): s=FROM_SET[value_type]([]) a=array.array(BUFFER_SIZE[value_type],[1]) result=ANY[value_type](a,s) self.assertEqual(result, False) def test_any_empty_set_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=[1] result=ANY_FROM_ITER[value_type](a,s) self.assertEqual(result, False) def test_noniter_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=1 with pytest.raises(TypeError) as context: ANY_FROM_ITER[value_type](a,s) self.assertTrue("object is not iterable" in str(context.value)) def test_memview_none(self, value_type): s=FROM_SET[value_type]([]) self.assertEqual(ANY[value_type](None,s), False) def test_dbnone(self, value_type): a=array.array(BUFFER_SIZE[value_type],[1]) self.assertEqual(ANY[value_type](a,None), False) def test_dbnone_from_iter(self, value_type): a=1 self.assertEqual(ANY_FROM_ITER[value_type](a,None), False) class TestAnyPyObject(UnitTestMock): def test_any_no(self): s=PyObjectSet_from([2,4,666]) a=np.array([1,3,333]*6, dtype=np.object) result=any_pyobject(a,s) self.assertEqual(result, False) def test_any_no_from_iter(self): s=PyObjectSet_from([2,4,666]) a=[1,3,333]*6 result=any_pyobject_from_iter(a,s) self.assertEqual(result, False) def test_any_last_yes(self): s=PyObjectSet_from([2,4,666]) a=np.array([1,3,333]*6+[2], dtype=np.object) result=any_pyobject(a,s) self.assertEqual(result, True) def test_any_last_yes_from_iter(self): s=PyObjectSet_from([2,4,666]) a=[1,3,333]*6+[2] result=any_pyobject_from_iter(a,s) self.assertEqual(result, True) def test_any_empty(self): s=PyObjectSet_from([]) a=np.array([], dtype=np.object) result=any_pyobject(a,s) self.assertEqual(result, False) def test_any_empty_from_iter(self): s=PyObjectSet_from([]) a=[] result=any_pyobject_from_iter(a,s) self.assertEqual(result, False) def test_any_empty_set(self): s=PyObjectSet_from([]) a=np.array([1], dtype=np.object) result=any_pyobject(a,s) self.assertEqual(result, False) def test_any_empty_set_from_iter(self): s=PyObjectSet_from([]) a=[1] result=any_pyobject_from_iter(a,s) self.assertEqual(result, False) def test_noniter_from_iter(self): s=PyObjectSet_from([]) a=1 with pytest.raises(TypeError) as context: any_pyobject_from_iter(a,s) self.assertTrue("object is not iterable" in str(context.value)) def test_memview_none(self): s=PyObjectSet_from([]) self.assertEqual(any_pyobject(None,s), False) def test_dbnone(self): a=np.array([1], dtype=np.object) self.assertEqual(any_pyobject(a,None), False) def test_dbnone_from_iter(self): a=1 self.assertEqual(any_pyobject_from_iter(a,None), False) cykhash-2.0.0/tests/unit_tests/test_count_if.py000066400000000000000000000132331414255425400217350ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock import numpy as np from cykhash import count_if_int64, count_if_int64_from_iter, Int64Set_from, Int64Set_from_buffer from cykhash import count_if_int32, count_if_int32_from_iter, Int32Set_from, Int32Set_from_buffer from cykhash import count_if_float64, count_if_float64_from_iter, Float64Set_from, Float64Set_from_buffer from cykhash import count_if_float32, count_if_float32_from_iter, Float32Set_from, Float32Set_from_buffer from cykhash import count_if_pyobject, count_if_pyobject_from_iter, PyObjectSet_from, PyObjectSet_from_buffer COUNT_IF={'int32': count_if_int32, 'int64': count_if_int64, 'float64' : count_if_float64, 'float32' : count_if_float32} COUNT_IF_FROM_ITER={'int32': count_if_int32_from_iter, 'int64': count_if_int64_from_iter, 'float64' : count_if_float64_from_iter, 'float32' : count_if_float32_from_iter} FROM_SET={'int32': Int32Set_from, 'int64': Int64Set_from, 'float64' : Float64Set_from, 'float32' : Float32Set_from, 'pyobject' : PyObjectSet_from} BUFFER_SIZE = {'int32': 'i', 'int64': 'q', 'float64' : 'd', 'float32' : 'f'} import array @pytest.mark.parametrize( "value_type", ['int64', 'int32', 'float64', 'float32'] ) class TestCountIf(UnitTestMock): def test_count_if_all(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], [2,4,6]*6) result=COUNT_IF[value_type](a,s) self.assertEqual(result, 18) def test_count_if_all_from_iter(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=[2,4,6]*6 result=COUNT_IF_FROM_ITER[value_type](a,s) self.assertEqual(result, 18) def test_count_if_but_last(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], [2]*6+[1]) result=COUNT_IF[value_type](a,s) self.assertEqual(result, 6) def test_count_if_but_last_from_iter(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=[2]*6+[1] result=COUNT_IF_FROM_ITER[value_type](a,s) self.assertEqual(result, 6) def test_count_if_empty(self, value_type): s=FROM_SET[value_type]([]) a=array.array(BUFFER_SIZE[value_type],[]) result=COUNT_IF[value_type](a,s) self.assertEqual(result, 0) def test_count_if_empty_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=[] result=COUNT_IF_FROM_ITER[value_type](a,s) self.assertEqual(result, 0) def test_count_if_empty_set(self, value_type): s=FROM_SET[value_type]([]) a=array.array(BUFFER_SIZE[value_type],[1]) result=COUNT_IF[value_type](a,s) self.assertEqual(result, 0) def test_count_if_empty_set_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=[1] result=COUNT_IF_FROM_ITER[value_type](a,s) self.assertEqual(result, 0) def test_noniter_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=1 with pytest.raises(TypeError) as context: COUNT_IF_FROM_ITER[value_type](a,s) self.assertTrue("object is not iterable" in str(context.value)) def test_memview_none(self, value_type): s=FROM_SET[value_type]([]) self.assertEqual(COUNT_IF[value_type](None,s), 0) def test_dbnone(self, value_type): a=array.array(BUFFER_SIZE[value_type],[1]) self.assertEqual(COUNT_IF[value_type](a,None), 0) def test_dbnone_from_iter(self, value_type): a=1 self.assertEqual(COUNT_IF_FROM_ITER[value_type](a,None), 0) class TestCountIfPyObject(UnitTestMock): def test_count_if_all(self): s=PyObjectSet_from([2,4,666]) a=np.array([2,4,666]*6, dtype=np.object) result=count_if_pyobject(a,s) self.assertEqual(result, 18) def test_count_if_all_from_iter(self): s=PyObjectSet_from([2,4,666]) a=[2,4,666]*6 result=count_if_pyobject_from_iter(a,s) self.assertEqual(result, 18) def test_count_if_but_last(self): s=PyObjectSet_from([2,4,666]) a=np.array([2,4,666]*6+[2, 1], dtype=np.object) result=count_if_pyobject(a,s) self.assertEqual(result, 19) def test_count_if_butlast_from_iter(self): s=PyObjectSet_from([2,4,666, "str"]) a=[2,4,666,"str"]*6+[2,1] result=count_if_pyobject_from_iter(a,s) self.assertEqual(result, 25) def test_count_if_empty(self): s=PyObjectSet_from([]) a=np.array([], dtype=np.object) result=count_if_pyobject(a,s) self.assertEqual(result, 0) def test_count_if_empty_from_iter(self): s=PyObjectSet_from([]) a=[] result=count_if_pyobject_from_iter(a,s) self.assertEqual(result, 0) def test_count_if_empty_set(self): s=PyObjectSet_from([]) a=np.array([1], dtype=np.object) result=count_if_pyobject(a,s) self.assertEqual(result, 0) def test_count_if_empty_set_from_iter(self): s=PyObjectSet_from([]) a=[1] result=count_if_pyobject_from_iter(a,s) self.assertEqual(result, 0) def test_noniter_from_iter(self): s=PyObjectSet_from([]) a=1 with pytest.raises(TypeError) as context: count_if_pyobject_from_iter(a,s) self.assertTrue("object is not iterable" in str(context.value)) def test_memview_none(self): s=PyObjectSet_from([]) self.assertEqual(count_if_pyobject(None,s), 0) def test_dbnone(self): a=np.array([1], dtype=np.object) self.assertEqual(count_if_pyobject(a,None), 0) def test_dbnone_from_iter(self): a=1 self.assertEqual(count_if_pyobject_from_iter(a,None), 0) cykhash-2.0.0/tests/unit_tests/test_create_maps.py000066400000000000000000000103411414255425400224070ustar00rootroot00000000000000from unittestmock import UnitTestMock import pytest import array from cykhash import Int64toInt64Map_from_buffers, Int64toFloat64Map_from_buffers from cykhash import Int32toInt32Map_from_buffers, Int32toFloat32Map_from_buffers from cykhash import Float64toInt64Map_from_buffers, Float64toFloat64Map_from_buffers from cykhash import Float32toInt32Map_from_buffers, Float32toFloat32Map_from_buffers from cykhash import PyObjectMap_from_buffers FUNCTION = {'int64_int64': Int64toInt64Map_from_buffers, 'int64_float64': Int64toFloat64Map_from_buffers, 'int32_int32': Int32toInt32Map_from_buffers, 'int32_float32': Int32toFloat32Map_from_buffers, 'float64_int64': Float64toInt64Map_from_buffers, 'float64_float64': Float64toFloat64Map_from_buffers, 'float32_int32': Float32toInt32Map_from_buffers, 'float32_float32': Float32toFloat32Map_from_buffers, } KEY_FORMAT = {'int64_int64': 'q', 'int64_float64': 'q', 'int32_int32': 'i', 'int32_float32': 'i', 'float64_int64': 'd', 'float64_float64': 'd', 'float32_int32': 'f', 'float32_float32': 'f', } VAL_FORMAT = {'int64_int64': 'q', 'int64_float64': 'd', 'int32_int32': 'i', 'int32_float32': 'f', 'float64_int64': 'q', 'float64_float64': 'd', 'float32_int32': 'i', 'float32_float32': 'f', } @pytest.mark.parametrize( "fun_type", ['int64_int64', 'int64_float64', 'int32_int32', 'int32_float32', 'float64_int64', 'float64_float64', 'float32_int32', 'float32_float32', ]) class TestMap_from_buffer(UnitTestMock): def test_create_from(self, fun_type): keys=array.array(KEY_FORMAT[fun_type], [1,2,3]) vals=array.array(VAL_FORMAT[fun_type], [4,5,6]) m=FUNCTION[fun_type](keys, vals, 2.0) self.assertEqual(len(m), len(keys)) for x,y in zip(keys, vals): self.assertTrue(x in m) self.assertEqual(m[x], y) def test_create_diff_vals_longer(self, fun_type): keys=array.array(KEY_FORMAT[fun_type], [1,2,3]) vals=array.array(VAL_FORMAT[fun_type], [4,5,6,7]) m=FUNCTION[fun_type](keys, vals, 2.0) self.assertEqual(len(m), len(keys)) for x,y in zip(keys, vals): self.assertTrue(x in m) self.assertEqual(m[x], y) def test_create_diff_keys_longer(self, fun_type): keys=array.array(KEY_FORMAT[fun_type], [1,2,3,42]) vals=array.array(VAL_FORMAT[fun_type], [4,5,6]) m=FUNCTION[fun_type](keys, vals, 2.0) self.assertEqual(len(m), len(vals)) for x,y in zip(keys, vals): self.assertTrue(x in m) self.assertEqual(m[x], y) class TestPyObject_from_buffers(UnitTestMock): def test_pyobject_create_from(self): try: import numpy as np except: return # well what should I do? keys=np.array([1,2,3], dtype=np.object) vals=np.array([4,5,6], dtype=np.object) m=PyObjectMap_from_buffers(keys, vals, 2.0) self.assertEqual(len(m), len(keys)) for x,y in zip(keys, vals): self.assertTrue(x in m) self.assertEqual(m[x], y) def test_create_diff_vals_longer(self): try: import numpy as np except: return # well what should I do? keys=np.array([1,2,3], dtype=np.object) vals=np.array([4,5,6,7], dtype=np.object) m=PyObjectMap_from_buffers(keys, vals, 2.0) self.assertEqual(len(m), len(keys)) for x,y in zip(keys, vals): self.assertTrue(x in m) self.assertEqual(m[x], y) def test_create_diff_keys_longer(self): try: import numpy as np except: return # well what should I do? keys=np.array([1,2,3, 42], dtype=np.object) vals=np.array([4,5,6], dtype=np.object) m=PyObjectMap_from_buffers(keys, vals, 2.0) self.assertEqual(len(m), len(vals)) for x,y in zip(keys, vals): self.assertTrue(x in m) self.assertEqual(m[x], y) cykhash-2.0.0/tests/unit_tests/test_cykhash_memory.py000066400000000000000000000071131414255425400231510ustar00rootroot00000000000000from contextlib import contextmanager import numpy as np import pytest import cykhash as cyk from cykhash.utils import get_cykhash_trace_domain import sys at_least_python36 = pytest.mark.skipif(sys.version_info < (3, 6), reason="requires Python3.6+") from cykhash.compat import PYPY not_on_pypy = pytest.mark.skipif(PYPY, reason="pypy doesn't support tracemalloc") if not PYPY: import tracemalloc @contextmanager def activated_tracemalloc(): tracemalloc.start() try: yield finally: tracemalloc.stop() def get_allocated_cykhash_memory(): snapshot = tracemalloc.take_snapshot() snapshot = snapshot.filter_traces( (tracemalloc.DomainFilter(True, get_cykhash_trace_domain()),) ) return sum(map(lambda x: x.size, snapshot.traces)) def test_trace_domain(): assert 414141 == get_cykhash_trace_domain() @not_on_pypy @at_least_python36 @pytest.mark.parametrize( "set_type, dtype", [ (cyk.Int64Set, np.int64), (cyk.Int32Set, np.int32), (cyk.Float64Set, np.float64), (cyk.Float32Set, np.float32), (cyk.PyObjectSet, np.object), ], ) def test_tracemalloc_works_sets(set_type, dtype): N = 2**10 keys = np.arange(N).astype(dtype) with activated_tracemalloc(): myset = set_type(keys) used = get_allocated_cykhash_memory() lower_bound = np.dtype(dtype).itemsize * (N*2) upper_bound = lower_bound * 1.1 assert used > lower_bound assert used < upper_bound del myset assert get_allocated_cykhash_memory() == 0 @not_on_pypy @at_least_python36 @pytest.mark.parametrize( "map_type, dtype", [ (cyk.Int64toInt64Map, np.int64), (cyk.Int32toInt32Map, np.int32), (cyk.Float64toFloat64Map, np.float64), (cyk.Float32toFloat32Map, np.float32), (cyk.PyObjectMap, np.object), ], ) def test_tracemalloc_works_maps(map_type, dtype): N = 2**10 keys = np.arange(N).astype(dtype) with activated_tracemalloc(): mymap = map_type(zip(keys, keys)) used = get_allocated_cykhash_memory() lower_bound = np.dtype(dtype).itemsize * (N*2*2) upper_bound = lower_bound * 1.05 assert used > lower_bound assert used < upper_bound del mymap assert get_allocated_cykhash_memory() == 0 @not_on_pypy @at_least_python36 @pytest.mark.parametrize( "unique_version, dtype", [ (cyk.unique_int64, np.int64), (cyk.unique_int32, np.int32), (cyk.unique_float64, np.float64), (cyk.unique_float32, np.float32), ], ) def test_unique_memory_consumption(unique_version, dtype): N = 2**10 keys = np.arange(N).astype(dtype) with activated_tracemalloc(): result_buffer = unique_version(keys) assert get_allocated_cykhash_memory() == np.dtype(dtype).itemsize * N del result_buffer assert get_allocated_cykhash_memory() == 0 @not_on_pypy @at_least_python36 @pytest.mark.parametrize( "unique_stable_version, dtype", [ (cyk.unique_stable_int64, np.int64), (cyk.unique_stable_int32, np.int32), (cyk.unique_stable_float64, np.float64), (cyk.unique_stable_float32, np.float32), ], ) def test_unique_memory_consumption(unique_stable_version, dtype): N = 2**10 keys = np.arange(N).astype(dtype) with activated_tracemalloc(): result_buffer = unique_stable_version(keys) assert get_allocated_cykhash_memory() == np.dtype(dtype).itemsize * N del result_buffer assert get_allocated_cykhash_memory() == 0 cykhash-2.0.0/tests/unit_tests/test_documentation_examples.py000066400000000000000000000071341414255425400247010ustar00rootroot00000000000000from unittestmock import UnitTestMock class TestDoc(UnitTestMock): def test_create_set_from_buffer(self): import numpy as np from cykhash import Int64Set_from_buffer a = np.arange(42, dtype=np.int64) my_set = Int64Set_from_buffer(a) # no reallocation will be needed assert 41 in my_set and 42 not in my_set self.assertTrue(True) def test_create_set_from_iterator(self): from cykhash import Int64Set_from my_set = Int64Set_from(range(42)) # no reallocation will be needed assert 41 in my_set and 42 not in my_set self.assertTrue(True) def test_is_in(self): import numpy as np from cykhash import Int64Set_from_buffer, isin_int64 a = np.arange(42, dtype=np.int64) lookup = Int64Set_from_buffer(a) b = np.arange(84, dtype=np.int64) result = np.empty(b.size, dtype=np.bool) isin_int64(b, lookup, result) assert np.sum(result.astype(np.int))==42 self.assertTrue(True) def test_unique(self): import numpy as np from cykhash import unique_int64 a = np.array([1,2,3,3,2,1], dtype=np.int64) u = np.ctypeslib.as_array(unique_int64(a)) # there will be no reallocation print(u) # [1,2,3] or any permutation of it self.assertTrue(True) def test_stable_unique(self): import numpy as np from cykhash import unique_stable_int64 a = np.array([3,2,1,1,2,3], dtype=np.int64) u = np.ctypeslib.as_array(unique_stable_int64(a)) # there will be no reallocation print(u) # [3,2,1] self.assertTrue(True) def test_int64map_from_buffer(self): import numpy as np from cykhash import Int64toFloat64Map_from_buffers keys = np.array([1, 2, 3, 4], dtype=np.int64) vals = np.array([5, 6, 7, 8], dtype=np.float64) my_map = Int64toFloat64Map_from_buffers(keys, vals) # there will be no reallocation assert my_map[4] == 8.0 self.assertTrue(True) def test_int64map_from_scrarch(self): import numpy as np from cykhash import Int64toInt64Map # my_map will not need reallocation for at least 12 elements my_map = Int64toInt64Map(number_of_elements_hint=12) for i in range(12): my_map[i] = i+1 assert my_map[5] == 6 self.assertTrue(True) def test_quick_tutorial_1(self): import numpy as np a = np.arange(42, dtype=np.int64) b = np.arange(84, dtype=np.int64) result = np.empty(b.size, dtype=np.bool) # actually usage from cykhash import Int64Set_from_buffer, isin_int64 lookup = Int64Set_from_buffer(a) # create a hashset isin_int64(b, lookup, result) # running time O(b.size) isin_int64(b, lookup, result) # lookup is reused and not recreated self.assertTrue(True) def test_quick_tutorial_2(self): import numpy as np a = np.array([1,2,3,3,2,1], dtype=np.int64) # actual usage: from cykhash import unique_int64 unique_buffer = unique_int64(a) # unique element are exposed via buffer-protocol # can be converted to a numpy-array without copying via unique_array = np.ctypeslib.as_array(unique_buffer) self.assertTrue(True) def test_quick_tutorial_3(self): from cykhash import Float64toInt64Map my_map = Float64toInt64Map() # values are 64bit integers my_map[float("nan")] = 1 assert my_map[float("nan")] == 1 self.assertTrue(True) cykhash-2.0.0/tests/unit_tests/test_isin.py000066400000000000000000000115321414255425400210710ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock import numpy as np from cykhash import isin_int64, Int64Set_from, Int64Set_from_buffer from cykhash import isin_int32, Int32Set_from, Int32Set_from_buffer from cykhash import isin_float64, Float64Set_from, Float64Set_from_buffer from cykhash import isin_float32, Float32Set_from, Float32Set_from_buffer from cykhash import isin_pyobject, PyObjectSet_from, PyObjectSet_from_buffer ISIN={'int32': isin_int32, 'int64': isin_int64, 'float64' : isin_float64, 'float32' : isin_float32} FROM_SET={'int32': Int32Set_from, 'int64': Int64Set_from, 'float64' : Float64Set_from, 'float32' : Float32Set_from, 'pyobject' : PyObjectSet_from} BUFFER_SIZE = {'int32': 'i', 'int64': 'q', 'float64' : 'd', 'float32' : 'f'} FROM_BUFFER_SET={'int32': Int32Set_from_buffer, 'int64': Int64Set_from_buffer, 'float64' : Float64Set_from_buffer, 'float32' : Float32Set_from_buffer, 'pyobject' : PyObjectSet_from_buffer} @pytest.mark.parametrize( "value_type", ['int64', 'int32', 'float64', 'float32', 'pyobject'] ) class TestInt64Set_from(UnitTestMock): def test_create(self, value_type): lst=[6,7,8] s=FROM_SET[value_type](list(lst)) self.assertEqual(len(s), len(lst)) for x in lst: self.assertTrue(x in s) import array @pytest.mark.parametrize( "value_type", ['int64', 'int32', 'float64', 'float32'] ) class TestBuffer(UnitTestMock): def test_isin(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], range(0,7)) result=array.array('B', [False]*7) ISIN[value_type](a,s,result) expected=array.array('B', [False, False, True, False, True, False, True]) self.assertTrue(expected==result) def test_isin_result_shorter(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], range(0,7)) result=array.array('B', [False]*6) with pytest.raises(ValueError) as context: ISIN[value_type](a,s,result) self.assertEqual("Different sizes for query(7) and result(6)", context.value.args[0]) def test_isin_result_longer(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], range(0,7)) result=array.array('B', [False]*8) with pytest.raises(ValueError) as context: ISIN[value_type](a,s,result) self.assertEqual("Different sizes for query(7) and result(8)", context.value.args[0]) def test_isin_db_none(self, value_type): a=array.array(BUFFER_SIZE[value_type], range(0,7)) result=array.array('B', [True]*7) ISIN[value_type](a,None,result) expected=array.array('B', [False, False, False, False, False, False, False]) self.assertTrue(expected==result) def test_isin_nones(self, value_type): s=FROM_SET[value_type]([2,4,6]) ISIN[value_type](None,s,None) self.assertTrue(True) def test_from_buffer(self, value_type): a=array.array(BUFFER_SIZE[value_type], [6,7,8]) s=FROM_BUFFER_SET[value_type](a) self.assertEqual(len(s), len(a)) for x in a: self.assertTrue(x in s) class TestBufferPyObject(UnitTestMock): def test_pyobject_isin(self): s=PyObjectSet_from([2,4,6]) a=np.array(range(0,7), dtype=np.object) result=array.array('B', [False]*7) isin_pyobject(a,s,result) expected=array.array('B', [False, False, True, False, True, False, True]) self.assertTrue(expected==result) def test_pyobject_from_buffer(self): a=np.array([6,7,8], dtype=np.object) s=PyObjectSet_from_buffer(a) self.assertEqual(len(s), len(a)) for x in a: self.assertTrue(x in s) def test_isin_result_shorter(self): s=PyObjectSet_from([2,4,6]) a=np.array(range(0,7), dtype=np.object) result=array.array('B', [False]*6) with pytest.raises(ValueError) as context: isin_pyobject(a,s,result) self.assertEqual("Different sizes for query(7) and result(6)", context.value.args[0]) def test_isin_result_longer(self): s=PyObjectSet_from([2,4,6]) a=np.array(range(0,7), dtype=np.object) result=array.array('B', [False]*8) with pytest.raises(ValueError) as context: isin_pyobject(a,s,result) self.assertEqual("Different sizes for query(7) and result(8)", context.value.args[0]) def test_isin_db_none(self): a=np.array(range(0,7), dtype=np.object) result=array.array('B', [True]*7) isin_pyobject(a,None,result) expected=array.array('B', [False, False, False, False, False, False, False]) self.assertTrue(expected==result) def test_isin_nones(self): s=PyObjectSet_from([2,4,6]) isin_pyobject(None,s,None) self.assertTrue(True) cykhash-2.0.0/tests/unit_tests/test_isin_random.py000066400000000000000000000027271414255425400224370ustar00rootroot00000000000000import pytest import numpy as np from cykhash import isin_int64, Int64Set_from, Int64Set_from_buffer from cykhash import isin_int32, Int32Set_from, Int32Set_from_buffer from cykhash import isin_float64, Float64Set_from, Float64Set_from_buffer from cykhash import isin_float32, Float32Set_from, Float32Set_from_buffer from cykhash import isin_pyobject, PyObjectSet_from, PyObjectSet_from_buffer ISIN={'int32': isin_int32, 'int64': isin_int64, 'float64' : isin_float64, 'float32' : isin_float32, 'pyobject' : isin_pyobject} NPTYPE = {'int32': np.int32, 'int64': np.int64, 'float64' : np.float64, 'float32' : np.float32, 'pyobject' : np.object_} FROM_BUFFER_SET={'int32': Int32Set_from_buffer, 'int64': Int64Set_from_buffer, 'float64' : Float64Set_from_buffer, 'float32' : Float32Set_from_buffer, 'pyobject' : PyObjectSet_from_buffer} @pytest.mark.parametrize( "value_type", ['int64', 'int32', 'float64', 'float32', 'pyobject'] ) def test_isin_random(value_type): np.random.seed(42) NMAX = 10000 for _ in range(50): n = np.random.randint(500, 2000,1)[0] values = np.random.randint(0, NMAX, n).astype(NPTYPE[value_type]) s=FROM_BUFFER_SET[value_type](values, .1) query = np.arange(NMAX).astype(NPTYPE[value_type]) expected = np.in1d(query, values) result = np.empty_like(expected) ISIN[value_type](query, s, result) assert np.array_equal(expected, result) cykhash-2.0.0/tests/unit_tests/test_map_dropin.py000066400000000000000000000315161414255425400222630ustar00rootroot00000000000000from unittestmock import UnitTestMock import pytest from cykhash import Int64toInt64Map, Int32toInt32Map, Float64toInt64Map, Float32toInt32Map, PyObjectMap from cykhash import Int64toFloat64Map, Int32toFloat32Map, Float64toFloat64Map, Float32toFloat32Map import cykhash nopython_maps = [Int64toInt64Map, Int32toInt32Map, Float64toInt64Map, Float32toInt32Map, Int64toFloat64Map, Int32toFloat32Map, Float64toFloat64Map, Float32toFloat32Map] all_maps = nopython_maps + [PyObjectMap] SUFFIX={Int64toInt64Map : "int64toint64map", Int32toInt32Map : "int32toint32map", Float64toInt64Map : "float64toint64map", Float32toInt32Map : "float32toint32map", Int64toFloat64Map : "int64tofloat64map", Int32toFloat32Map : "int32tofloat32map", Float64toFloat64Map : "float64tofloat64map", Float32toFloat32Map : "float32tofloat32map", PyObjectMap : "pyobjectmap"} def pick_fun(name, map_type): return getattr(cykhash, name+"_"+SUFFIX[map_type]) @pytest.mark.parametrize( "map_type", all_maps ) class TestMapDropin(UnitTestMock): def test_init_int_from_iter(self, map_type): m=map_type([(1,2),(3,1)]) self.assertEqual(len(m), 2) self.assertEqual(m[1],2) self.assertEqual(m[3],1) def test_fromkeys(self, map_type): m=map_type.fromkeys([1,2,3], 55) self.assertEqual(len(m), 3) self.assertEqual(m[1],55) self.assertEqual(m[2],55) self.assertEqual(m[3],55) def test_clear(self, map_type): a=map_type([(1,2), (2,3)]) a.clear() self.assertEqual(len(a), 0) a[5]=6 self.assertEqual(a[5], 6) a.clear() self.assertEqual(len(a), 0) def test_iterable(self, map_type): a=map_type([(1,4), (2,3)]) keys = list(a) self.assertEqual(set(keys), {1,2}) def test_setintem_getitem_delitem(self, map_type): a=map_type() a[5] = 42 self.assertEqual(len(a), 1) self.assertTrue(a) self.assertEqual(a[5], 42) del a[5] self.assertEqual(len(a), 0) self.assertFalse(a) with pytest.raises(KeyError) as context: a[5] self.assertEqual(context.value.args[0], 5) with pytest.raises(KeyError) as context: a[5] self.assertEqual(context.value.args[0], 5) def test_get(self, map_type): a=map_type([(1,2)]) self.assertEqual(a.get(1), 2) self.assertTrue(a.get(2) is None) self.assertEqual(a.get(1, 5), 2) self.assertEqual(a.get(2, 5), 5) with pytest.raises(TypeError) as context: a.get(1,2,4) self.assertEqual("get() expected at most 2 arguments, got 3", context.value.args[0]) with pytest.raises(TypeError) as context: a.get(1,default=9) self.assertEqual("get() takes no keyword arguments", context.value.args[0]) with pytest.raises(TypeError) as context: a.get() self.assertEqual("get() expected at least 1 arguments, got 0", context.value.args[0]) def test_pop(self, map_type): a=map_type([(1,2)]) self.assertTrue(a.pop(2, None) is None) self.assertEqual(a.pop(1, 5), 2) self.assertEqual(len(a), 0) self.assertEqual(a.pop(1, 5), 5) self.assertEqual(a.pop(2, 5), 5) with pytest.raises(KeyError) as context: a.pop(1) self.assertEqual(1, context.value.args[0]) with pytest.raises(TypeError) as context: a.pop(1,2,4) self.assertEqual("pop() expected at most 2 arguments, got 3", context.value.args[0]) with pytest.raises(TypeError) as context: a.pop(1,default=9) self.assertEqual("pop() takes no keyword arguments", context.value.args[0]) with pytest.raises(TypeError) as context: a.pop() self.assertEqual("pop() expected at least 1 arguments, got 0", context.value.args[0]) def test_popitem(self, map_type): a=map_type([(1,2)]) self.assertEqual(a.popitem(), (1,2)) self.assertEqual(len(a), 0) with pytest.raises(KeyError) as context: a.popitem() self.assertEqual("popitem(): dictionary is empty", context.value.args[0]) def test_popitem2(self, map_type): a=map_type(zip(range(1000), range(1000))) s=set() for i in range(1000): s.add(a.popitem()) self.assertEqual(len(a), 0) self.assertEqual(s, set(zip(range(1000), range(1000)))) def test_setdefault(self, map_type): a=map_type() self.assertEqual(a.setdefault(2,4), 4) self.assertEqual(a.setdefault(2,5), 4) self.assertEqual(len(a), 1) self.assertEqual(a[2], 4) self.assertEqual(a.setdefault(4,4444), 4444) self.assertEqual(len(a), 2) @pytest.mark.parametrize( "map_type", all_maps ) class TestSwap(UnitTestMock): def test_with_none(self, map_type): swap=pick_fun("swap", map_type) a=map_type([(1,2),(3,1)]) with pytest.raises(TypeError) as context: swap(None,a) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: swap(a,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: swap(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_swap(self, map_type): swap=pick_fun("swap", map_type) a=map_type([(1,2),(3,1),(5,3)]) b=map_type([(2,3),(4,6)]) swap(a,b) self.assertEqual(len(a), 2) self.assertEqual(a[2], 3) self.assertEqual(a[4], 6) self.assertEqual(len(b), 3) self.assertEqual(b[1], 2) self.assertEqual(b[3], 1) self.assertEqual(b[5], 3) def test_empty(self, map_type): swap=pick_fun("swap", map_type) a=map_type(zip(range(1000), range(1000))) b=map_type() swap(a,b) self.assertEqual(len(a), 0) self.assertEqual(len(b), 1000) a[6]=3 self.assertEqual(len(a), 1) b[1000]=0 self.assertEqual(len(b), 1001) @pytest.mark.parametrize( "map_type", all_maps ) class TestDictView(UnitTestMock): def test_len(self, map_type): a=map_type([(1,2),(3,1),(5,3)]) self.assertEqual(len(a.keys()), 3) self.assertEqual(len(a.items()), 3) self.assertEqual(len(a.values()), 3) def test_in(self, map_type): a=map_type([(1,2),(3,1),(5,3)]) self.assertEqual(5 in a.keys(), True) self.assertEqual(6 in a.keys(), False) self.assertEqual(2 in a.values(), True) self.assertEqual(6 in a.values(), False) self.assertEqual((3,1) in a.items(), True) self.assertEqual((5,5) in a.items(), False) @pytest.mark.parametrize( "map_type", all_maps ) class TestAreEqual(UnitTestMock): def test_with_none(self, map_type): s=map_type([(1,1),(2,2),(3,3),(1,1)]) are_equal=pick_fun("are_equal", map_type) with pytest.raises(TypeError) as context: are_equal(None,s) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: are_equal(s,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: are_equal(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_with_empty(self, map_type): a=map_type() b=map_type() are_equal=pick_fun("are_equal", map_type) self.assertEqual(are_equal(a,b), True) self.assertEqual(are_equal(b,a), True) self.assertEqual(a==b, True) self.assertEqual(b==a, True) def test_with_one_empty(self, map_type): a=map_type([(1,2)]) b=map_type() are_equal=pick_fun("are_equal", map_type) self.assertEqual(are_equal(a,b), False) self.assertEqual(are_equal(b,a), False) self.assertEqual(a==b, False) self.assertEqual(b==a, False) def test_small_yes(self, map_type): a=map_type([(1,2), (3,4)]) b=map_type([(3,4), (1,2)]) are_equal=pick_fun("are_equal", map_type) self.assertEqual(are_equal(a,b), True) self.assertEqual(are_equal(b,a), True) self.assertEqual(a==b, True) self.assertEqual(b==a, True) def test_small_no(self, map_type): a=map_type([(1,2), (3,4)]) b=map_type([(3,4), (2,2)]) are_equal=pick_fun("are_equal", map_type) self.assertEqual(are_equal(a,b), False) self.assertEqual(are_equal(b,a), False) self.assertEqual(a==b, False) self.assertEqual(b==a, False) def test_small_no_diffsizes(self, map_type): a=map_type([(1,2), (3,4), (3,4)]) b=map_type([(3,4), (2,2), (3,3)]) are_equal=pick_fun("are_equal", map_type) self.assertEqual(are_equal(a,b), False) self.assertEqual(are_equal(b,a), False) self.assertEqual(a==b, False) self.assertEqual(b==a, False) def test_large_method(self, map_type): a=map_type(zip(range(33,10000,3), range(33,10000,3))) b=map_type(zip(range(33,10000,3), range(33,10000,3))) are_equal=pick_fun("are_equal", map_type) self.assertEqual(are_equal(a,b), True) self.assertEqual(are_equal(b,a), True) self.assertEqual(a==b, True) self.assertEqual(b==a, True) @pytest.mark.parametrize( "map_type", all_maps ) class TestCopy(UnitTestMock): def test_with_none(self, map_type): copy=pick_fun("copy", map_type) self.assertTrue(copy(None) is None) def test_with_empty(self, map_type): a=map_type([]) copy=pick_fun("copy", map_type) self.assertEqual(len(copy(a)), 0) def test_small(self, map_type): a=map_type([(1,1),(2,2),(3,3),(1,1)]) copy=pick_fun("copy", map_type) self.assertEqual(copy(a)==a, True) def test_large(self, map_type): a=map_type(zip(range(33,10000,3), range(33,10000,3))) copy=pick_fun("copy", map_type) self.assertEqual(copy(a)==a, True) def test_large_method(self, map_type): a=map_type(zip(range(33,10000,3), range(33,10000,3))) self.assertEqual(a.copy()==a, True) @pytest.mark.parametrize( "map_type", all_maps ) class TestUpdate(UnitTestMock): def test_with_none(self, map_type): update=pick_fun("update", map_type) a=map_type([(1,2),(3,1)]) with pytest.raises(TypeError) as context: update(None,a) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: update(a,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: update(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_update(self, map_type): update=pick_fun("update", map_type) a=map_type([(1,2),(3,1)]) b=map_type([(1,3),(4,6)]) b_copy=b.copy() update(a,b) self.assertEqual(a, map_type([(1,3), (3,1), (4,6)])) self.assertEqual(b, b_copy) def test_update_empty(self, map_type): update=pick_fun("update", map_type) a=map_type([(1,2),(3,1)]) a_copy=a.copy() b=map_type([]) update(a,b) self.assertEqual(a, a_copy) self.assertEqual(len(b), 0) update(b,a) self.assertEqual(b, a_copy) self.assertEqual(a, a_copy) def test_update_large(self, map_type): update=pick_fun("update", map_type) a=map_type(zip(range(1000), range(1,995))) b=map_type(zip(range(2000), range(2000))) update(a,b) self.assertEqual(a, b) def test_update_method(self, map_type): a=map_type([(1,2),(3,1)]) b=map_type([(1,3),(4,6)]) b_copy=b.copy() a.update(b) self.assertEqual(a, map_type([(1,3), (3,1), (4,6)])) self.assertEqual(b, b_copy) def test_update_from_iter(self, map_type): a=map_type([(1,2),(3,1)]) a.update([(1,3),(4,6)]) self.assertEqual(a, map_type([(1,3), (3,1), (4,6)])) def test_update_from_wrong_iter(self, map_type): a=map_type([(1,2),(3,1)]) with pytest.raises(ValueError) as context: a.update([(1,3),(4,6,4)]) self.assertEqual("too many values to unpack (expected 2)", context.value.args[0]) with pytest.raises(ValueError) as context: a.update([(1,3),(4,)]) self.assertEqual("need more than 1 value to unpack", context.value.args[0]) cykhash-2.0.0/tests/unit_tests/test_map_to.py000066400000000000000000000200151414255425400214020ustar00rootroot00000000000000from unittestmock import UnitTestMock import pytest import numpy as np from cykhash import Int64toInt64Map_to, Int64toInt64Map_from_buffers from cykhash import Int64toFloat64Map_to, Int64toFloat64Map_from_buffers from cykhash import Float64toInt64Map_to, Float64toInt64Map_from_buffers from cykhash import Float64toFloat64Map_to, Float64toFloat64Map_from_buffers from cykhash import Int32toInt32Map_to, Int32toInt32Map_from_buffers from cykhash import Int32toFloat32Map_to, Int32toFloat32Map_from_buffers from cykhash import Float32toInt32Map_to, Float32toInt32Map_from_buffers from cykhash import Float32toFloat32Map_to, Float32toFloat32Map_from_buffers from cykhash import PyObjectMap_to, PyObjectMap_from_buffers MAP_TO_INT = {'int32': Int32toInt32Map_to, 'int64': Int64toInt64Map_to, 'float64' : Float64toInt64Map_to, 'float32' : Float32toInt32Map_to} MAP_TO_FLOAT = {'int32': Int32toFloat32Map_to, 'int64': Int64toFloat64Map_to, 'float64' : Float64toFloat64Map_to, 'float32' : Float32toFloat32Map_to} CREATOR_FROM_INT = {'int32': Int32toInt32Map_from_buffers, 'int64': Int64toInt64Map_from_buffers, 'float64' : Float64toInt64Map_from_buffers, 'float32' : Float32toInt32Map_from_buffers} CREATOR_FROM_FLOAT = {'int32': Int32toFloat32Map_from_buffers, 'int64': Int64toFloat64Map_from_buffers, 'float64' : Float64toFloat64Map_from_buffers, 'float32' : Float32toFloat32Map_from_buffers} DTYPE = {'int32': np.int32, 'int64': np.int64, 'float64' : np.float64, 'float32' : np.float32} INT_DTYPE = {'int32': np.int32, 'int64': np.int64, 'float64' : np.int64, 'float32' : np.int32} FLOAT_DTYPE = {'int32': np.float32, 'int64': np.float64, 'float64' : np.float64, 'float32' : np.float32} @pytest.mark.parametrize( "value_type", ['int64', 'int32', 'float64', 'float32'] ) class TestMapTo(UnitTestMock): def test_None_map(self, value_type): k=np.array([]).astype(DTYPE[value_type]) ints=np.array([]).astype(INT_DTYPE[value_type]) floats=np.array([]).astype(FLOAT_DTYPE[value_type]) with pytest.raises(TypeError) as context: MAP_TO_INT[value_type](None,k,ints) self.assertTrue("'NoneType' is not a map" in context.value.args[0]) with pytest.raises(TypeError) as context: MAP_TO_FLOAT[value_type](None,k,floats) self.assertTrue("'NoneType' is not a map" in context.value.args[0]) def test_different_lengths_ints(self, value_type): N = 1000 keys=np.arange(N).astype(DTYPE[value_type]) ints=np.array(range(0,2*N,2)).astype(INT_DTYPE[value_type]) mymap = CREATOR_FROM_INT[value_type](keys, ints) results=np.zeros(N+1).astype(INT_DTYPE[value_type]) with pytest.raises(ValueError) as context: MAP_TO_INT[value_type](mymap, keys, results) self.assertTrue("Different lengths" in context.value.args[0]) def test_different_lengths_floats(self, value_type): N = 1000 keys=np.arange(N).astype(DTYPE[value_type]) floats=np.array(range(0,2*N,2)).astype(FLOAT_DTYPE[value_type]) mymap = CREATOR_FROM_FLOAT[value_type](keys, floats) results=np.zeros(N+1).astype(FLOAT_DTYPE[value_type]) with pytest.raises(ValueError) as context: MAP_TO_FLOAT[value_type](mymap, keys, results) self.assertTrue("Different lengths" in context.value.args[0]) def test_map_to_int_simple(self, value_type): N = 1000 keys=np.arange(N).astype(DTYPE[value_type]) vals=np.array(range(0,2*N,2)).astype(INT_DTYPE[value_type]) mymap = CREATOR_FROM_INT[value_type](keys, vals) result = np.zeros_like(vals) self.assertEqual(MAP_TO_INT[value_type](mymap, keys, result), N) self.assertTrue(np.array_equal(vals, result)) def test_map_to_float_simple(self, value_type): N = 1000 keys=np.arange(N).astype(DTYPE[value_type]) vals=np.array(range(0,2*N,2)).astype(FLOAT_DTYPE[value_type]) mymap = CREATOR_FROM_FLOAT[value_type](keys, vals) result = np.zeros_like(vals) self.assertEqual(MAP_TO_FLOAT[value_type](mymap, keys, result), N) self.assertTrue(np.array_equal(vals, result)) def test_map_with_stop_int(self, value_type): keys=np.arange(3).astype(DTYPE[value_type]) vals=np.array([5,6,7]).astype(INT_DTYPE[value_type]) mymap = CREATOR_FROM_INT[value_type](keys, vals) query = np.array([2,55,1]).astype(DTYPE[value_type]) result = np.zeros(query.shape, dtype=INT_DTYPE[value_type]) self.assertEqual(MAP_TO_INT[value_type](mymap, query, result), 1) self.assertEqual(result[0], vals[-1]) def test_map_with_stop_float(self, value_type): keys=np.arange(3).astype(DTYPE[value_type]) vals=np.array([5,6,7]).astype(FLOAT_DTYPE[value_type]) mymap = CREATOR_FROM_FLOAT[value_type](keys, vals) query = np.array([2,55,1]).astype(DTYPE[value_type]) result = np.zeros(query.shape, dtype=FLOAT_DTYPE[value_type]) self.assertEqual(MAP_TO_FLOAT[value_type](mymap, query, result), 1) self.assertEqual(result[0], vals[-1]) def test_map_no_stop_int(self, value_type): keys=np.arange(3).astype(DTYPE[value_type]) vals=np.array([5,6,7]).astype(INT_DTYPE[value_type]) mymap = CREATOR_FROM_INT[value_type](keys, vals) query = np.array([2,55,1,66,0]).astype(DTYPE[value_type]) result = np.zeros(query.shape, dtype=INT_DTYPE[value_type]) expected = np.array([7,42,6,42,5]).astype(INT_DTYPE[value_type]) self.assertEqual(MAP_TO_INT[value_type](mymap, query, result, False, 42), 3) self.assertTrue(np.array_equal(expected, result)) def test_map_no_stop_float(self, value_type): keys=np.arange(3).astype(DTYPE[value_type]) vals=np.array([5,6,7]).astype(FLOAT_DTYPE[value_type]) mymap = CREATOR_FROM_FLOAT[value_type](keys, vals) query = np.array([2,55,1,66,0]).astype(DTYPE[value_type]) result = np.zeros(query.shape, dtype=FLOAT_DTYPE[value_type]) expected = np.array([7,42,6,42,5]).astype(FLOAT_DTYPE[value_type]) self.assertEqual(MAP_TO_FLOAT[value_type](mymap, query, result, False, 42), 3) self.assertTrue(np.array_equal(expected, result)) class TestMapToPyObject(UnitTestMock): def test_None_map(self): objs=np.array([]).astype(np.object) with pytest.raises(TypeError) as context: PyObjectMap_to(None,objs,objs) self.assertTrue("'NoneType' is not a map" in context.value.args[0]) def test_different_lengths(self): N = 1000 keys=np.arange(N).astype(np.object) mymap = PyObjectMap_from_buffers(keys, keys) results=np.zeros(N+1).astype(np.object) with pytest.raises(ValueError) as context: PyObjectMap_to(mymap, keys, results) self.assertTrue("Different lengths" in context.value.args[0]) def test_map_to_simple(self): N = 1000 keys=np.arange(N).astype(np.object) vals=np.array(range(0,2*N,2)).astype(np.object) mymap = PyObjectMap_from_buffers(keys, vals) result = np.zeros_like(vals) self.assertEqual(PyObjectMap_to(mymap, keys, result), N) self.assertTrue(np.array_equal(vals,result)) def test_map_with_stop(self): keys=np.arange(3).astype(np.object) vals=np.array([5,6,7]).astype(np.object) mymap = PyObjectMap_from_buffers(keys, vals) query = np.array([2,55,1]).astype(np.object) result = np.zeros_like(query) self.assertEqual(PyObjectMap_to(mymap, query, result), 1) self.assertEqual(result[0], vals[-1]) def test_map_no_stop_float(self): keys=np.arange(3).astype(np.object) vals=np.array([5,6,7]).astype(np.object) mymap = PyObjectMap_from_buffers(keys, vals) query = np.array([2,55,1,66,0]).astype(np.object) result = np.zeros_like(query) expected = np.array([7,42,6,42,5]).astype(np.object) self.assertEqual(PyObjectMap_to(mymap, query, result, False, 42), 3) self.assertTrue(np.array_equal(expected, result)) cykhash-2.0.0/tests/unit_tests/test_none.py000066400000000000000000000127151414255425400210720ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock import numpy as np from cykhash import none_int64, none_int64_from_iter, Int64Set_from, Int64Set_from_buffer from cykhash import none_int32, none_int32_from_iter, Int32Set_from, Int32Set_from_buffer from cykhash import none_float64, none_float64_from_iter, Float64Set_from, Float64Set_from_buffer from cykhash import none_float32, none_float32_from_iter, Float32Set_from, Float32Set_from_buffer from cykhash import none_pyobject, none_pyobject_from_iter, PyObjectSet_from, PyObjectSet_from_buffer NONE={'int32': none_int32, 'int64': none_int64, 'float64' : none_float64, 'float32' : none_float32} NONE_FROM_ITER={'int32': none_int32_from_iter, 'int64': none_int64_from_iter, 'float64' : none_float64_from_iter, 'float32' : none_float32_from_iter} FROM_SET={'int32': Int32Set_from, 'int64': Int64Set_from, 'float64' : Float64Set_from, 'float32' : Float32Set_from, 'pyobject' : PyObjectSet_from} BUFFER_SIZE = {'int32': 'i', 'int64': 'q', 'float64' : 'd', 'float32' : 'f'} import array @pytest.mark.parametrize( "value_type", ['int64', 'int32', 'float64', 'float32'] ) class TestNone(UnitTestMock): def test_none_yes(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], [1,3,5]*6) result=NONE[value_type](a,s) self.assertEqual(result, True) def test_none_yes_from_iter(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=[1,3,5]*6 result=NONE_FROM_ITER[value_type](a,s) self.assertEqual(result, True) def test_none_last_no(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=array.array(BUFFER_SIZE[value_type], [1]*6+[2]) result=NONE[value_type](a,s) self.assertEqual(result, False) def test_none_last_no_from_iter(self, value_type): s=FROM_SET[value_type]([2,4,6]) a=[1]*6+[2] result=NONE_FROM_ITER[value_type](a,s) self.assertEqual(result, False) def test_none_empty(self, value_type): s=FROM_SET[value_type]([]) a=array.array(BUFFER_SIZE[value_type],[]) result=NONE[value_type](a,s) self.assertEqual(result, True) def test_none_empty_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=[] result=NONE_FROM_ITER[value_type](a,s) self.assertEqual(result, True) def test_none_empty_set(self, value_type): s=FROM_SET[value_type]([]) a=array.array(BUFFER_SIZE[value_type],[1]) result=NONE[value_type](a,s) self.assertEqual(result, True) def test_none_empty_set_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=[1] result=NONE_FROM_ITER[value_type](a,s) self.assertEqual(result, True) def test_noniter_from_iter(self, value_type): s=FROM_SET[value_type]([]) a=1 with pytest.raises(TypeError) as context: NONE_FROM_ITER[value_type](a,s) self.assertTrue("object is not iterable" in str(context.value)) def test_memview_none(self, value_type): s=FROM_SET[value_type]([]) self.assertEqual(NONE[value_type](None,s), True) def test_dbnone(self, value_type): a=array.array(BUFFER_SIZE[value_type],[1]) self.assertEqual(NONE[value_type](a,None), True) def test_dbnone_from_iter(self, value_type): a=1 self.assertEqual(NONE_FROM_ITER[value_type](a,None), True) class TestNonePyObject(UnitTestMock): def test_none_yes(self): s=PyObjectSet_from([2,4,666]) a=np.array([1,3,333]*6, dtype=np.object) result=none_pyobject(a,s) self.assertEqual(result, True) def test_none_from_iter(self): s=PyObjectSet_from([2,4,666]) a=[1,3,333]*6 result=none_pyobject_from_iter(a,s) self.assertEqual(result, True) def test_none_last_no(self): s=PyObjectSet_from([2,4,666]) a=np.array([1,3,333]*6+[2], dtype=np.object) result=none_pyobject(a,s) self.assertEqual(result, False) def test_none_last_no_from_iter(self): s=PyObjectSet_from([2,4,666]) a=[1,3,333]*6+[2] result=none_pyobject_from_iter(a,s) self.assertEqual(result, False) def test_none_empty(self): s=PyObjectSet_from([]) a=np.array([], dtype=np.object) result=none_pyobject(a,s) self.assertEqual(result, True) def test_none_empty_from_iter(self): s=PyObjectSet_from([]) a=[] result=none_pyobject_from_iter(a,s) self.assertEqual(result, True) def test_none_empty_set(self): s=PyObjectSet_from([]) a=np.array([1], dtype=np.object) result=none_pyobject(a,s) self.assertEqual(result, True) def test_none_empty_set_from_iter(self): s=PyObjectSet_from([]) a=[1] result=none_pyobject_from_iter(a,s) self.assertEqual(result, True) def test_noniter_from_iter(self): s=PyObjectSet_from([]) a=1 with pytest.raises(TypeError) as context: none_pyobject_from_iter(a,s) self.assertTrue("object is not iterable" in str(context.value)) def test_memview_none(self): s=PyObjectSet_from([]) self.assertEqual(none_pyobject(None,s), True) def test_dbnone(self): a=np.array([1], dtype=np.object) self.assertEqual(none_pyobject(a,None), True) def test_dbnone_from_iter(self): a=1 self.assertEqual(none_pyobject_from_iter(a,None), True) cykhash-2.0.0/tests/unit_tests/test_set_dropin.py000066400000000000000000000635621414255425400223070ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock from cykhash import Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet import cykhash SUFFIX={Int64Set : "int64", Int32Set : "int32", Float64Set : "float64", Float32Set : "float32", PyObjectSet : "pyobject"} def pick_fun(name, set_type): return getattr(cykhash, name+"_"+SUFFIX[set_type]) @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestSetDropIn(UnitTestMock): def test_init_from_iter(self, set_type): s=set_type([1,2,3,1]) self.assertEqual(len(s), 3) self.assertTrue(1 in s) self.assertTrue(2 in s) self.assertTrue(3 in s) def test_clear(self, set_type): s=set_type([1,2,3,1]) s.clear() self.assertEqual(len(s), 0) s.add(5) s.update([3,4,5,6]) self.assertEqual(s, set_type([3,4,5,6])) s.clear() self.assertEqual(len(s), 0) def test_str(self, set_type): s=set_type([1,2,3,1]) ss = str(s) self.assertTrue("1" in ss) self.assertTrue("2" in ss) self.assertTrue("3" in ss) self.assertTrue(ss.startswith("{")) self.assertTrue(ss.endswith("}")) def test_remove_yes(self, set_type): s=set_type([1,2]) s.remove(1) self.assertEqual(s,set_type([2])) s.remove(2) self.assertEqual(s,set_type([])) def test_remove_no(self, set_type): s=set_type([1,2]) with pytest.raises(KeyError) as context: s.remove(3) self.assertEqual(3, context.value.args[0]) def test_pop_one(self, set_type): s=set_type([1]) el=s.pop() self.assertEqual(s,set_type([])) self.assertEqual(el,1) def test_pop_all(self, set_type): s=set_type([1,2,3]) new_s={s.pop(), s.pop(), s.pop()} self.assertEqual(s,set_type([])) self.assertEqual(new_s,{1,2,3}) def test_pop_empty(self, set_type): s=set_type([]) with pytest.raises(KeyError) as context: s.pop() self.assertEqual("pop from empty set", context.value.args[0]) def test_pyobject_same_object_pop(): a=float("3333.2") s=PyObjectSet([a]) b=s.pop() assert a is b @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestIsDisjoint(UnitTestMock): def test_aredisjoint_with_none(self, set_type): s=set_type([1,2,3,1]) fun=pick_fun("aredisjoint", set_type) with pytest.raises(TypeError) as context: fun(None,s) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: fun(s,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: fun(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_aredisjoint_with_empty(self, set_type): empty1=set_type() empty2=set_type() non_empty=set_type(range(3)) aredisjoint=pick_fun("aredisjoint", set_type) self.assertEqual(aredisjoint(empty1, non_empty), True) self.assertEqual(aredisjoint(non_empty, empty2), True) self.assertEqual(aredisjoint(empty1, empty2), True) def test_aredisjoint_yes(self, set_type): a=set_type([1,2,3,1]) b=set_type([4,55]) fun=pick_fun("aredisjoint", set_type) self.assertEqual(fun(a,b), True) self.assertEqual(fun(b,a), True) def test_aredisjoint_no(self, set_type): a=set_type([1,2,3,333,1]) b=set_type([4,55,4,5,6,7,333]) fun=pick_fun("aredisjoint", set_type) self.assertEqual(fun(a,b), False) self.assertEqual(fun(b,a), False) def test_isdisjoint_yes_set(self, set_type): a=set_type([1,2,3,1]) b=set_type([4,55]) self.assertEqual(a.isdisjoint(b), True) self.assertEqual(b.isdisjoint(a), True) def test_isdisjoint_no_set(self, set_type): a=set_type([1,2,3,333,1]) b=set_type([4,55,4,5,6,7,333]) self.assertEqual(a.isdisjoint(b), False) self.assertEqual(b.isdisjoint(a), False) def test_isdisjoint_yes_iter(self, set_type): a=set_type([1,2,3,1]) b=[4,55] self.assertEqual(a.isdisjoint(b), True) def test_isdisjoint_no_iter(self, set_type): a=set_type([1,2,3,333,1]) b=[4,55,4,5,6,7,333] self.assertEqual(a.isdisjoint(b), False) @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestIsSubsetIsSuperset(UnitTestMock): def test_with_none(self, set_type): s=set_type([1,2,3,1]) fun=pick_fun("issubset", set_type) with pytest.raises(TypeError) as context: fun(None,s) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: fun(s,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: fun(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_with_empty(self, set_type): a=set_type([1,2,3,1]) b=set_type([]) fun=pick_fun("issubset", set_type) self.assertEqual(fun(a,a), True) self.assertEqual(fun(a,b), True) self.assertEqual(fun(b,a), False) self.assertEqual(fun(b,b), True) def test_yes(self, set_type): a=set_type([1,2,3,1]) b=set_type([1,3]) fun=pick_fun("issubset", set_type) self.assertEqual(fun(a,b), True) self.assertEqual(fun(b,a), False) def test_no(self, set_type): a=set_type([1,2,3,1]) b=set_type([4]) fun=pick_fun("issubset", set_type) self.assertEqual(fun(a,b), False) self.assertEqual(fun(b,a), False) def test_issuperset_yes(self, set_type): a=set_type([1,2,3,1]) b=set_type([1,3]) self.assertEqual(a.issuperset(b), True) self.assertEqual(b.issuperset(a), False) def test_issuperset_no(self, set_type): a=set_type([1,2,3,1]) b=set_type([4]) self.assertEqual(a.issuperset(b), False) self.assertEqual(b.issuperset(a), False) def test_issuperset_yes_iter(self, set_type): a=set_type([1,2,3,1]) b=[1,3] self.assertEqual(a.issuperset(b), True) def test_issuperset_no_iter(self, set_type): a=set_type([1,2,3,1]) b=[4] self.assertEqual(a.issuperset(b), False) def test_issubset_yes_iter(self, set_type): a=set_type([1,2]) b=[1,3,2] self.assertEqual(a.issubset(b), True) def test_issubset_no_iter(self, set_type): a=set_type([1,2]) b=[1,1,3] self.assertEqual(a.issubset(b), False) def test_issubset_yes(self, set_type): a=set_type([1,2]) b=set_type([1,3,2]) self.assertEqual(a.issubset(b), True) self.assertEqual(b.issubset(a), False) def test_issubset_no(self, set_type): a=set_type([1,2]) b=set_type([1,1,3]) self.assertEqual(a.issubset(b), False) self.assertEqual(b.issubset(a), False) def test_compare_self(self, set_type): a=set_type([1,2]) self.assertEqual(a<=a, True) self.assertEqual(a>=a, True) self.assertEqual(aa, False) def test_compare_no_relation(self, set_type): a=set_type([1,2]) b=set_type([1,3]) self.assertEqual(a<=b, False) self.assertEqual(a>=b, False) self.assertEqual(ab, False) def test_compare_real_subset(self, set_type): a=set_type([1,2,3]) b=set_type([1,3]) self.assertEqual(a<=b, False) self.assertEqual(a>=b, True) self.assertEqual(ab, True) def test_compare_same(self, set_type): a=set_type([1,3]) b=set_type([1,3]) self.assertEqual(a<=b, True) self.assertEqual(a>=b, True) self.assertEqual(ab, False) def test_compare_equal_yes(self, set_type): a=set_type([2,5,7,8,1,3]) b=set_type([1,3,7,7,7,7,7,2,5,8,8,8,8,8,8]) self.assertEqual(a==b, True) self.assertEqual(a==b, True) def test_compare_equal_yes(self, set_type): a=set_type([2,5,7,8,1,3]) b=set_type([3,7,7,7,7,7,2,5,8,8,8,8,8,8]) self.assertEqual(a==b, False) self.assertEqual(a==b, False) @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestCopy(UnitTestMock): def test_with_none(self, set_type): s=set_type([1,2,3,1]) copy=pick_fun("copy", set_type) self.assertTrue(copy(None) is None) def test_with_empty(self, set_type): a=set_type([]) copy=pick_fun("copy", set_type) self.assertEqual(len(copy(a)), 0) def test_small(self, set_type): a=set_type([1,2,3,1]) copy=pick_fun("copy", set_type) self.assertEqual(copy(a)==a, True) def test_large(self, set_type): a=set_type(range(33,10000,3)) copy=pick_fun("copy", set_type) self.assertEqual(copy(a)==a, True) def test_large_method(self, set_type): a=set_type(range(33,10000,3)) self.assertEqual(a.copy()==a, True) @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestUpdate(UnitTestMock): def test_with_none(self, set_type): s=set_type([1,2,3,1]) update=pick_fun("update", set_type) with pytest.raises(TypeError) as context: update(None,s) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: update(s,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: update(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_some_common(self, set_type): a=set_type([1,2,3,4]) b=set_type([2,1,2,5]) c=b.copy() update=pick_fun("update", set_type) update(a,b) self.assertEqual(a, set_type([1,2,3,4,5])) self.assertEqual(b, c) def test_with_itself(self, set_type): a=set_type([1,2,3,1]) b=a.copy() update=pick_fun("update", set_type) update(a,a) self.assertEqual(a, b) def test_with_disjunct(self, set_type): a=set_type(range(50)) b=set_type(range(50,100)) update=pick_fun("update", set_type) update(a,b) self.assertEqual(a, set_type(range(100))) def test_method_with_set(self, set_type): a=set_type(range(50)) b=set_type(range(100)) a.update(b) self.assertEqual(a, set_type(range(100))) def test_method_with_set(self, set_type): a=set_type(range(50)) b=set_type(range(100)) a.update(b) self.assertEqual(a, set_type(range(100))) def test_method_with_iterator(self, set_type): a=set_type(range(50)) a.update(range(60)) self.assertEqual(a, set_type(range(60))) def test_ior(self, set_type): a=set_type(range(50)) a|=set_type(range(60)) self.assertEqual(a, set_type(range(60))) def test_union(self, set_type): a=set_type(range(30)) a_copy = a.copy() b=a.union(range(30,40), set_type(range(40,50)), range(50,60)) self.assertEqual(b, set_type(range(60))) self.assertEqual(a, a_copy) def test_union_empty(self, set_type): a=set_type(range(30)) a.union() self.assertEqual(a, set_type(range(30))) def test_or(self, set_type): a=set_type(range(30)) b=set_type(range(30,40)) c=set_type(range(40,50)) d=a|b|c self.assertEqual(d, set_type(range(50))) self.assertEqual(a, set_type(range(30))) self.assertEqual(b, set_type(range(30,40))) self.assertEqual(c, set_type(range(40,50))) @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestSwap(UnitTestMock): def test_with_none(self, set_type): s=set_type([1,2,3,1]) swap=pick_fun("swap", set_type) with pytest.raises(TypeError) as context: swap(None,s) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: swap(s,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: swap(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_some_common(self, set_type): a=set_type([1,2,3,4]) b=set_type([5,2,4]) a_copy=a.copy() b_copy=b.copy() swap=pick_fun("swap", set_type) swap(a,b) self.assertEqual(a, b_copy) self.assertEqual(b, a_copy) swap(a,b) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestIntersect(UnitTestMock): def test_with_none(self, set_type): s=set_type([1,2,3,1]) intersect=pick_fun("intersect", set_type) with pytest.raises(TypeError) as context: intersect(None,s) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: intersect(s,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: intersect(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_small(self, set_type): a=set_type([1,2,3,4]) b=set_type([5,2,4]) a_copy=a.copy() b_copy=b.copy() intersect=pick_fun("intersect", set_type) c=intersect(a,b) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([2,4])) c=intersect(b,a) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([2,4])) def test_disjunct(self, set_type): a=set_type([1,3,5,7,9]) b=set_type([2,2,4,6,8,10]) a_copy=a.copy() b_copy=b.copy() intersect=pick_fun("intersect", set_type) c=intersect(a,b) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type()) c=intersect(b,a) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([])) def test_empty(self, set_type): a=set_type([]) b=set_type([]) c=set_type([2,2,4,6,8,10]) intersect=pick_fun("intersect", set_type) d=intersect(a,b) self.assertEqual(len(d), 0) d=intersect(c,b) self.assertEqual(len(d), 0) d=intersect(a,c) self.assertEqual(len(d), 0) def test_intersection_update(self, set_type): a=set_type([1,2,3,4,5,6,7,8]) b=set_type([2,4,6,8,10,12]) b_copy = b.copy() a.intersection_update(b) self.assertEqual(a, set_type([2,4,6,8])) self.assertEqual(b, b_copy) def test_intersection_update_iter(self, set_type): a=set_type([1,2,3,4,5,6,7,8]) a.intersection_update([2,4,6,8,10,12]) self.assertEqual(a, set_type([2,4,6,8])) def test_empty_update(self, set_type): a=set_type([1,2,3,4,5,6,7,8]) b=set_type([]) a.intersection_update(b) self.assertEqual(len(a), 0) def test_empty_update_iter(self, set_type): a=set_type([1,2,3,4,5,6,7,8]) a.intersection_update([]) self.assertEqual(a, set_type()) def test_iadd(self, set_type): a=set_type([1,2,3,4,5,6,7,8]) b=set_type([1,104,3]) a&=b self.assertEqual(a, set_type([1,3])) def test_add(self, set_type): a=set_type([1,2,3,4,5,6,7,8]) b=set_type([1,104,3]) a_copy=a.copy() b_copy=b.copy() c=a&b self.assertEqual(c, set_type([1,3])) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) def test_intersection(self, set_type): a=set_type([1,2,3,4,5,6,7,8]) a_copy=a.copy() c=a.intersection([1,2,3,4,5,6], set_type([1,2,3,4,5]), [1,2,3]) self.assertEqual(c, set_type([1,2,3])) self.assertEqual(a, a_copy) @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestDifference(UnitTestMock): def test_with_none(self, set_type): s=set_type([1,2,3,1]) difference=pick_fun("difference", set_type) with pytest.raises(TypeError) as context: difference(None,s) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: difference(s,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: difference(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_small(self, set_type): a=set_type([1,2,3,4]) b=set_type([5,2,4]) a_copy=a.copy() b_copy=b.copy() difference=pick_fun("difference", set_type) c=difference(a,b) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([1,3])) c=difference(b,a) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([5])) def test_disjunct(self, set_type): a=set_type([1,3,5,7,9]) b=set_type([2,2,4,6,8,10]) a_copy=a.copy() b_copy=b.copy() difference=pick_fun("difference", set_type) c=difference(a,b) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, a) c=difference(b,a) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, b) def test_empty(self, set_type): a=set_type([]) b=set_type([]) c=set_type([2,2,4,6,8,10]) difference=pick_fun("difference", set_type) d=difference(a,b) self.assertEqual(len(d), 0) d=difference(c,b) self.assertEqual(c, d) d=difference(a,c) self.assertEqual(len(d), 0) def test_method_update(self, set_type): a=set_type([1,2,3,4]) b=set_type([5,2,4]) b_copy=b.copy() a.difference_update(b) self.assertEqual(b, b_copy) self.assertEqual(a, set_type([1,3])) def test_method_update2(self, set_type): a=set_type([1,2,3,4]) b=set_type([5,2,4]) a_copy=a.copy() b.difference_update(a) self.assertEqual(a, a_copy) self.assertEqual(b, set_type([5])) def test_method_update_from_iter(self, set_type): a=set_type([1,2,3,4]) a.difference_update([5,2,4]) self.assertEqual(a, set_type([1,3])) def test_method_update_from_iter2(self, set_type): a=set_type(range(1000)) a.difference_update(range(0,1000,2)) self.assertEqual(a, set_type(range(1,1000,2))) def test_method_update_from_iter3(self, set_type): a=set_type([1,2]) a.difference_update([1]*10000) self.assertEqual(a, set_type([2])) def test_sub(self, set_type): a=set_type([0,222,3,444,5]) b=set_type([222,3,4]) a_copy=a.copy() b_copy=b.copy() c=a-b self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([0,444,5])) c=b-a self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([4])) def test_sub2(self, set_type): a=set_type([1,2,3,4]) a_copy=a.copy() b=a-a-a-a self.assertEqual(a, a_copy) self.assertEqual(b, set_type()) def test_isub(self, set_type): a=set_type([0,222,3,444,5]) b=set_type([222,3,4]) b_copy=b.copy() a-=b self.assertEqual(b, b_copy) self.assertEqual(a, set_type([0,444,5])) def test_isub2(self, set_type): a=set_type([1,2,3,4]) a-=a self.assertEqual(a, set_type()) def test_difference_method(self, set_type): a=set_type(range(10000)) a_copy=a.copy() b=a.difference(range(5000), set_type(range(5000,10000,2)), range(1,9999,2)) self.assertEqual(b, set_type([9999])) self.assertEqual(a, a_copy) @pytest.mark.parametrize( "set_type", [Int64Set, Int32Set, Float64Set, Float32Set, PyObjectSet] ) class TestSymmetricDifference(UnitTestMock): def test_with_none(self, set_type): s=set_type([1,2,3,1]) symmetric_difference=pick_fun("symmetric_difference", set_type) with pytest.raises(TypeError) as context: symmetric_difference(None,s) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: symmetric_difference(s,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) with pytest.raises(TypeError) as context: symmetric_difference(None,None) self.assertTrue("'NoneType' object is not iterable" in context.value.args[0]) def test_small(self, set_type): a=set_type([1,2,3,4]) b=set_type([5,2,4]) a_copy=a.copy() b_copy=b.copy() symmetric_difference=pick_fun("symmetric_difference", set_type) c=symmetric_difference(a,b) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([1,3,5])) c=symmetric_difference(b,a) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([1,3,5])) def test_disjunct(self, set_type): a=set_type([1,3,5,7,9]) b=set_type([2,2,4,6,8,10]) a_copy=a.copy() b_copy=b.copy() symmetric_difference=pick_fun("symmetric_difference", set_type) c=symmetric_difference(a,b) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, a|b) c=symmetric_difference(b,a) self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, a|b) def test_empty(self, set_type): a=set_type([]) b=set_type([]) c=set_type([2,2,4,6,8,10]) symmetric_difference=pick_fun("symmetric_difference", set_type) d=symmetric_difference(a,b) self.assertEqual(len(d), 0) d=symmetric_difference(c,b) self.assertEqual(c, d) d=symmetric_difference(a,c) self.assertEqual(c, d) def test_method_update(self, set_type): a=set_type([1,2,3,4]) b=set_type([5,2,4]) b_copy=b.copy() a.symmetric_difference_update(b) self.assertEqual(b, b_copy) self.assertEqual(a, set_type([1,3,5])) def test_method_update2(self, set_type): a=set_type([1,2,3,4]) b=set_type([5,2,4]) a_copy=a.copy() b.symmetric_difference_update(a) self.assertEqual(a, a_copy) self.assertEqual(b, set_type([1,3,5])) def test_method_update_from_iter(self, set_type): a=set_type([1,2,3,4]) a.symmetric_difference_update([5,2,4]) self.assertEqual(a, set_type([1,3, 5])) def test_method_update_from_iter2(self, set_type): a=set_type(range(1000)) a.symmetric_difference_update(range(0,1000,2)) self.assertEqual(a, set_type(range(1,1000,2))) def test_method_update_from_iter3(self, set_type): a=set_type([1,2]) a.symmetric_difference_update([1]*10000) self.assertEqual(a, set_type([2])) def test_method_update_from_iter4(self, set_type): a=set_type([1,2]) a.symmetric_difference_update(a) self.assertEqual(len(a), 0) def test_xor(self, set_type): a=set_type([0,222,3,444,5]) b=set_type([222,3,4]) a_copy=a.copy() b_copy=b.copy() c=a^b self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([0,444,5,4])) c=b^a self.assertEqual(a, a_copy) self.assertEqual(b, b_copy) self.assertEqual(c, set_type([0,444,5,4])) def test_xor2(self, set_type): a=set_type([1,2,3,4]) a_copy=a.copy() b=a^a^a^a self.assertEqual(a, a_copy) self.assertEqual(len(b), 0) def test_xor3(self, set_type): a=set_type([1,2,3,4]) a_copy=a.copy() b=a^a^a^a^a self.assertEqual(a, a_copy) self.assertEqual(b, a) def test_ixor(self, set_type): a=set_type([0,222,3,444,5]) b=set_type([222,3,4]) b_copy=b.copy() a^=b self.assertEqual(b, b_copy) self.assertEqual(a, set_type([0,444,5,4])) def test_ixor2(self, set_type): a=set_type([1,2,3,4]) a^=a self.assertEqual(a, set_type()) def test_symmetric_method(self, set_type): a=set_type(range(10)) a_copy=a.copy() b=a.symmetric_difference(range(5,15), set_type(range(5,10)), range(1,16)) self.assertEqual(b, set_type([0,15])) self.assertEqual(a, a_copy) cykhash-2.0.0/tests/unit_tests/test_unique.py000066400000000000000000000100621414255425400214320ustar00rootroot00000000000000import pytest from unittestmock import UnitTestMock import pyximport; pyximport.install(setup_args = {"script_args" : ["--force"]}, language_level=3) from cykhash import unique_int64, unique_int32, unique_float64, unique_float32 from cykhash import unique_stable_int64, unique_stable_int32, unique_stable_float64, unique_stable_float32 from uniqueinterfacetester import use_unique_int64, use_unique_int32, use_unique_float64, use_unique_float32 from uniqueinterfacetester import use_unique_stable_int64, use_unique_stable_int32, use_unique_stable_float64, use_unique_stable_float32 UNIQUE={'int64': unique_int64, 'int32': unique_int32, 'float64': unique_float64, 'float32': unique_float32, } STABLE={'int64': unique_stable_int64, 'int32': unique_stable_int32, 'float64': unique_stable_float64, 'float32': unique_stable_float32, } CY_UNIQUE={'int64': use_unique_int64, 'int32': use_unique_int32, 'float64': use_unique_float64, 'float32': use_unique_float32, } CY_STABLE={'int64': use_unique_stable_int64, 'int32': use_unique_stable_int32, 'float64': use_unique_stable_float64, 'float32': use_unique_stable_float32, } BUFFER_SIZE = {'int32': 'i', 'int64': 'q', 'float64' : 'd', 'float32' : 'f'} import array @pytest.mark.parametrize( "value_type", ['int64', 'int32', 'float64', 'float32'] ) class TestUniqueTester(UnitTestMock): def test_unique(self, value_type): a = array.array(BUFFER_SIZE[value_type], [1,1,1,1,1,2,3,4,5]) result = UNIQUE[value_type](a) as_set = set(memoryview(result)) expected = set(array.array(BUFFER_SIZE[value_type], [1,2,3,4,5])) self.assertTrue(expected==as_set, msg = "received: "+str(as_set)) def test_unique_stable(self, value_type): a = array.array(BUFFER_SIZE[value_type], [2,1,4,1,3,1,2,3,4,5]) result = list(memoryview(STABLE[value_type](a))) expected = list([2,1,4,3,5]) self.assertTrue(expected==result, msg = "received: "+str(result)) def test_unique2(self, value_type): a = array.array(BUFFER_SIZE[value_type], list(range(100))+list(range(200))+list(range(100))) result = UNIQUE[value_type](a) as_set = set(memoryview(result)) expected = set(array.array(BUFFER_SIZE[value_type], range(200))) self.assertTrue(expected==as_set, msg = "received: "+str(as_set)) def test_cyunique(self, value_type): a = array.array(BUFFER_SIZE[value_type], list(range(100))+list(range(200))+list(range(100))) result = CY_UNIQUE[value_type](a) as_set = set(memoryview(result)) expected = set(array.array(BUFFER_SIZE[value_type], range(200))) self.assertTrue(expected==as_set, msg = "received: "+str(as_set)) def test_cyunique_stable(self, value_type): a = array.array(BUFFER_SIZE[value_type], [2,1,4,1,3,1,2,3,4,5]) result = list(memoryview(CY_STABLE[value_type](a))) expected = list([2,1,4,3,5]) self.assertTrue(expected==result, msg = "received: "+str(result)) def test_ctypeslib_as_array(self, value_type): try: import numpy as np except: return a = array.array(BUFFER_SIZE[value_type], list(range(100))+list(range(200))+list(range(100))) result = CY_UNIQUE[value_type](a) as_set = set(np.ctypeslib.as_array(result)) expected = set(array.array(BUFFER_SIZE[value_type], range(200))) self.assertTrue(expected==as_set, msg = "received: "+str(as_set)) def test_frombuffer(self, value_type): try: import numpy as np except: return a = array.array(BUFFER_SIZE[value_type], list(range(100))+list(range(200))+list(range(100))) result = CY_UNIQUE[value_type](a) as_set = set(np.frombuffer(result, dtype=BUFFER_SIZE[value_type])) expected = set(array.array(BUFFER_SIZE[value_type], range(200))) self.assertTrue(expected==as_set, msg = "received: "+str(as_set)) cykhash-2.0.0/tests/unit_tests/test_utils.py000066400000000000000000000042021414255425400212630ustar00rootroot00000000000000from unittestmock import UnitTestMock from cykhash.utils import float64_hash, float32_hash, int64_hash, int32_hash, object_hash, objects_are_equal from cykhash.compat import assert_if_not_on_PYPY, assert_equal_32_or_64 class TestUtils(UnitTestMock): def test_hash_float64_neg_zero(self): self.assertEqual(float64_hash(0.0), float64_hash(-0.0)) def test_hash_float32_neg_zero(self): self.assertEqual(float32_hash(0.0), float32_hash(-0.0)) def test_hash_float64_one(self): assert_equal_32_or_64( val=float64_hash(1.0), expected32=741795711, expected64=1954243739, reason="different murmur implementations" ) def test_hash_float32_one(self): self.assertEqual(float32_hash(1.0), 1648074928) def test_hash_int64_zero(self): assert_equal_32_or_64( val=int64_hash(0), expected32=224447722, expected64=4178429809, reason="different murmur implementations" ) def test_hash_int32_zero(self): self.assertEqual(int32_hash(0), 649440278) def test_hash_int64_one(self): assert_equal_32_or_64( val=int64_hash(1), expected32=190876766, expected64=1574219535, reason="different murmur implementations" ) def test_hash_int32_one(self): self.assertEqual(int32_hash(1), 1753268367) def test_hash_object_zero(self): self.assertEqual(object_hash(0), 0) def test_hash_object_one(self): assert_equal_32_or_64( val=object_hash(1), expected32=1, expected64=2049, reason="no additional hashing for 32bit and pyobjects" ) def test_equal_tupples_with_nan(self): t1 = (1, (float("nan"), 2), (4, (float("nan"),))) t2 = (1, (float("nan"), 2), (4, (float("nan"),))) assert_if_not_on_PYPY(t1 != t2, "nan is a singleton on PyPy") assert objects_are_equal(t1, t2) cykhash-2.0.0/tests/unit_tests/test_version.py000066400000000000000000000004751414255425400216200ustar00rootroot00000000000000from unittestmock import UnitTestMock import cykhash class TestVersion(UnitTestMock): def test_major(self): self.assertEqual(cykhash.__version__[0], 2) def test_minor(self): self.assertEqual(cykhash.__version__[1], 0) def test_last(self): self.assertEqual(cykhash.__version__[2], 0) cykhash-2.0.0/tests/unit_tests/uniqueinterfacetester.pyx000066400000000000000000000014521414255425400236760ustar00rootroot00000000000000 ############# int64 - test from cykhash.unique cimport unique_int64, unique_int32, unique_float64, unique_float32 from cykhash.unique cimport unique_stable_int64, unique_stable_int32, unique_stable_float64, unique_stable_float32 def use_unique_int64(vals): return unique_int64(vals, .2) def use_unique_stable_int64(vals): return unique_stable_int64(vals, .2) def use_unique_int32(vals): return unique_int32(vals, .2) def use_unique_stable_int32(vals): return unique_stable_int32(vals, .2) def use_unique_float64(vals): return unique_float64(vals, .2) def use_unique_stable_float64(vals): return unique_stable_float64(vals, .2) def use_unique_float32(vals): return unique_float32(vals, .2) def use_unique_stable_float32(vals): return unique_stable_float32(vals, .2) cykhash-2.0.0/tests/unit_tests/unittestmock.py000066400000000000000000000006711414255425400216230ustar00rootroot00000000000000class UnitTestMock: def assertEqual(self, a, b, **kwds): if "msg" in kwds: assert a == b, kwds["msg"] else: assert a == b def assertFalse(self, a, **kwds): if "msg" in kwds: assert not a, kwds["msg"] else: assert not a def assertTrue(self, a, **kwds): if "msg" in kwds: assert a, kwds["msg"] else: assert a