diff --git a/.circleci/config.yml b/.circleci/config.yml index 67b37f7187..1b34dbda45 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -121,6 +121,12 @@ commands: make allchecks python -m pytest -n2 + - run: + name: Build cython example LWT interface code and run + command: | + cd python/lwt_interface/cython_example + make + - run: name: Generate coverage command: | diff --git a/python/lwt_interface/cython_example/Makefile b/python/lwt_interface/cython_example/Makefile new file mode 100644 index 0000000000..7b0350eda9 --- /dev/null +++ b/python/lwt_interface/cython_example/Makefile @@ -0,0 +1,13 @@ +all: compile run + +compile: + pip install -e . --use-pep517 + +run: + PYTHONPATH=../.. python -c "import example; example.main()" + +clean: + rm -rf build/ + rm -rf tskit_cython_example.egg-info/ + rm -f example.c + rm -f *.so diff --git a/python/lwt_interface/cython_example/_lwtc.c b/python/lwt_interface/cython_example/_lwtc.c new file mode 100644 index 0000000000..a667d9a01d --- /dev/null +++ b/python/lwt_interface/cython_example/_lwtc.c @@ -0,0 +1,64 @@ +/* + * MIT License + * + * Copyright (c) 2019-2020 Tskit Developers + * Copyright (c) 2015-2018 University of Oxford + * + * 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. + */ +// Turn off clang-formatting for this file as turning off formatting +// for specific bits will make it more confusing. +// clang-format off + +#define PY_SSIZE_T_CLEAN +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION + +#include +#include +#include + +#include "kastore.h" +#include "tskit.h" + +#include "tskit_lwt_interface.h" + +static PyMethodDef lwt_methods[] = { + { NULL, NULL, 0, NULL } /* sentinel */ +}; + +static struct PyModuleDef lwt_module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "_lwt", + .m_doc = "tskit LightweightTableCollection", + .m_size = -1, + .m_methods = lwt_methods }; + +PyMODINIT_FUNC +PyInit__lwtc(void) +{ + PyObject *module = PyModule_Create(&lwt_module); + if (module == NULL) { + return NULL; + } + import_array(); + if (register_lwt_class(module) != 0) { + return NULL; + } + return module; +} diff --git a/python/lwt_interface/cython_example/example.pyx b/python/lwt_interface/cython_example/example.pyx new file mode 100644 index 0000000000..22a95045e4 --- /dev/null +++ b/python/lwt_interface/cython_example/example.pyx @@ -0,0 +1,63 @@ +from libc.stdint cimport uint32_t +import _lwtc +import tskit + +cdef extern from "tskit.h" nogil: + ctypedef uint32_t tsk_flags_t + ctypedef struct tsk_table_collection_t: + pass + ctypedef struct tsk_treeseq_t: + pass + int tsk_treeseq_init(tsk_treeseq_t *self, const tsk_table_collection_t *tables, tsk_flags_t options) + int tsk_treeseq_free(tsk_treeseq_t *self) + int tsk_table_collection_build_index(tsk_table_collection_t *self, tsk_flags_t options) + ctypedef struct tsk_tree_t: + pass + int tsk_tree_init(tsk_tree_t *self, const tsk_treeseq_t *ts, tsk_flags_t options) + int tsk_tree_first(tsk_tree_t *self) + int tsk_tree_next(tsk_tree_t *self) + int tsk_tree_last(tsk_tree_t *self) + int tsk_tree_prev(tsk_tree_t *self) + int tsk_tree_get_num_roots(tsk_tree_t *self) + int tsk_tree_free(tsk_tree_t *self) + const char *tsk_strerror(int err) + +cdef extern: + ctypedef class _lwtc.LightweightTableCollection [object LightweightTableCollection]: + cdef tsk_table_collection_t *tables + +def check_tsk_error(val): + if val < 0: + raise RuntimeError(tsk_strerror(val)) + +def iterate_trees(pyts: tskit.TreeSequence): + lwtc = LightweightTableCollection() + lwtc.fromdict(pyts.dump_tables().asdict()) + cdef tsk_treeseq_t ts + err = tsk_treeseq_init(&ts, lwtc.tables, 0) + check_tsk_error(err) + cdef tsk_tree_t tree + ret = tsk_tree_init(&tree, &ts, 0) + check_tsk_error(ret) + + print("Iterate forwards") + cdef int tree_iter = tsk_tree_first(&tree) + while tree_iter == 1: + print("\ttree has %d roots" % (tsk_tree_get_num_roots(&tree))) + tree_iter = tsk_tree_next(&tree) + check_tsk_error(tree_iter) + + print("Iterate backwards") + tree_iter = tsk_tree_last(&tree) + while tree_iter == 1: + print("\ttree has %d roots" % (tsk_tree_get_num_roots(&tree))) + tree_iter = tsk_tree_prev(&tree) + check_tsk_error(tree_iter) + + tsk_tree_free(&tree) + tsk_treeseq_free(&ts) + +def main(): + import msprime as msp # (msprime could be compiled against a different version of tskit) + ts = msp.simulate(sample_size=5, length=100, recombination_rate=.01) + iterate_trees(ts) diff --git a/python/lwt_interface/cython_example/pyproject.toml b/python/lwt_interface/cython_example/pyproject.toml new file mode 100644 index 0000000000..fa8cff5945 --- /dev/null +++ b/python/lwt_interface/cython_example/pyproject.toml @@ -0,0 +1,13 @@ +[build-system] +requires = ["setuptools>=64", "wheel", "Cython", "numpy"] +build-backend = "setuptools.build_meta" + +[project] +name = "tskit_cython_example" +version = "0.0.1" +description = "Cython example for tskit" +authors = [{name = "tskit developers"}] +dependencies = ["numpy", "Cython"] + +[tool.setuptools] +packages = [] diff --git a/python/lwt_interface/cython_example/setup.py b/python/lwt_interface/cython_example/setup.py new file mode 100644 index 0000000000..b83458128f --- /dev/null +++ b/python/lwt_interface/cython_example/setup.py @@ -0,0 +1,40 @@ +import glob +import os + +import numpy as np +from Cython.Build import cythonize +from setuptools import setup +from setuptools.extension import Extension + +TSKIT_BASE = os.path.join(os.path.dirname(__file__), "..", "..", "..") +TSKIT_C_PATH = os.path.join(TSKIT_BASE, "c") +TSKIT_PY_PATH = os.path.join(TSKIT_BASE, "python/lwt_interface") +KASTORE_PATH = os.path.join(TSKIT_BASE, "c", "subprojects", "kastore") +include_dirs = [TSKIT_C_PATH, TSKIT_PY_PATH, KASTORE_PATH, np.get_include()] + +tskit_sourcefiles = list(glob.glob(os.path.join(TSKIT_C_PATH, "tskit", "*.c"))) + [ + os.path.join(KASTORE_PATH, "kastore.c") +] + +extensions = [ + Extension( + "_lwtc", + ["_lwtc.c"] + tskit_sourcefiles, + language="c", + include_dirs=include_dirs, + ), + Extension( + "example", + ["example.pyx"] + tskit_sourcefiles, + language="c", + include_dirs=include_dirs, + ), +] + +extensions = cythonize(extensions, language_level=3) + +setup( + name="tskit_cython_example", + version="0.0.1", + ext_modules=extensions, +)