diff --git a/.gitignore b/.gitignore index 02eb0ab..0235229 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ tmp build dist *egg-info +__pycache__ diff --git a/Makefile b/Makefile index ed785ea..26eb1b0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ CFLAGS = -Iincludes -I/Users/ehasanaj/miniconda3/envs/ml38/include/python3.8 -std=c++17 -DDEBUG -D_FORTIFY_SOURCE=2 \ -D_GLIBCXX_ASSERTIONS -O2 -Wall CC = g++ -SRC = $(wildcard src/multiset_multipacking/*.cpp) +SRC = $(wildcard src/multiset_multicover/*.cpp) +SRC := $(filter-out src/multiset_multicover/python_interface.cpp, $(SRC)) OBJ = $(SRC:.cpp = .o) all: TestSet clean diff --git a/includes/BaseSet.h b/includes/BaseSet.h index ef3989f..0ed5c8d 100644 --- a/includes/BaseSet.h +++ b/includes/BaseSet.h @@ -1,7 +1,7 @@ #ifndef BASESET_H #define BASESET_H -#include // min_element, max_element +#include // min_element, max_element, copy #include // size_t #include // ostream #include @@ -13,6 +13,8 @@ using std::vector; class BaseSet { public: BaseSet(const vector& elements); + BaseSet(const BaseSet& other); + BaseSet& operator=(const BaseSet& other); const size_t& operator[](size_t index) const; size_t size() const; size_t min() const; @@ -21,8 +23,8 @@ class BaseSet { friend std::ostream& operator<<(std::ostream& os, const BaseSet& bs); protected: - const size_t _n_elements; - const vector _elements; + size_t _n_elements; + vector _elements; }; #endif // BASESET_H \ No newline at end of file diff --git a/includes/GreedyCover.h b/includes/GreedyCover.h index a77e410..043cf7b 100644 --- a/includes/GreedyCover.h +++ b/includes/GreedyCover.h @@ -32,6 +32,7 @@ class GreedyCoverInstance { void add_multiset(const vector& elements); void add_multiset(const vector& elements, const vector& mult); + void delete_multiset(size_t index); vector cover(size_t coverage); vector cover(size_t coverage, size_t max_iters); vector cover(const vector& coverage); @@ -54,7 +55,8 @@ class GreedyCoverInstance { vector _coverage_idx; // coverage factors for each element individually private: - void __update_max_coverage(const MultiSet& mset); + void __increase_max_coverage(const MultiSet& mset); + void __decrease_max_coverage(size_t index); void __init_leftovers(); void __update_leftovers(const MultiSet& mset); void __init_remaining_msets(); @@ -62,6 +64,7 @@ class GreedyCoverInstance { bool __stop() const; void __check_elements(const vector& elements) const; vector __cover(); + size_t __current_coverage() const; }; #endif // GREEDYCOVER_H \ No newline at end of file diff --git a/includes/MultiSet.h b/includes/MultiSet.h index 2257856..71ce1a2 100644 --- a/includes/MultiSet.h +++ b/includes/MultiSet.h @@ -30,10 +30,10 @@ class MultiSet : public BaseSet { protected: size_t _value = 0; vector _leftovers; - const vector _multiplicity; + vector _multiplicity; private: - const size_t __maxel; + size_t __maxel; void __init_leftovers(); vector __default_multiplicity(); }; diff --git a/includes/python_interface.h b/includes/python_interface.h index afb0c4c..4fdcc48 100644 --- a/includes/python_interface.h +++ b/includes/python_interface.h @@ -1,12 +1,16 @@ #ifndef PYTHON_INTERFACE_H #define PYTHON_INTERFACE_H +#define PY_SSIZE_T_CLEAN #include #include // size_t #include +#include +#include using std::size_t; using std::vector; +using std::string; #include "GreedyCover.h" @@ -24,20 +28,21 @@ PyObject* create_list_from_size_t_vector(const vector& v); extern "C" { #endif -PyObject* _new_GreedyCoverInstance(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _new_GreedyCoverInstance(PyObject* self, PyObject* args, PyObject* keywds); -PyObject* _GreedyCoverInstance_size(PyObject* self, PyObject* args, PyObject* keywds); -PyObject* _GreedyCoverInstance_n_elements(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance_size(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance_n_elements(PyObject* self, PyObject* args, PyObject* keywds); -PyObject* _GreedyCoverInstance_get_max_coverage(PyObject* self, PyObject* args, PyObject* keywds); -PyObject* _GreedyCoverInstance_get_leftovers(PyObject* self, PyObject* args, PyObject* keywds); -PyObject* _GreedyCoverInstance_get_multisets_incomplete_cover(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance_get_max_coverage(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance_get_leftovers(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance_get_multisets_incomplete_cover(PyObject* self, PyObject* args, PyObject* keywds); -PyObject* _GreedyCoverInstance_add_multiset(PyObject* self, PyObject* args, PyObject* keywds); -PyObject* _GreedyCoverInstance_cover(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance_add_multiset(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance_delete_multiset(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance_cover(PyObject* self, PyObject* args, PyObject* keywds); -PyObject* _GreedyCoverInstance_solution(PyObject* self, PyObject* args, PyObject* keywds); -PyObject* _GreedyCoverInstance__coverage_until(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance_solution(PyObject* self, PyObject* args, PyObject* keywds); +static PyObject* _GreedyCoverInstance__coverage_until(PyObject* self, PyObject* args, PyObject* keywds); static PyMethodDef gci_methods[] = { { "_new_GreedyCoverInstance", (PyCFunction)_new_GreedyCoverInstance, METH_VARARGS | METH_KEYWORDS, "" }, @@ -47,6 +52,7 @@ static PyMethodDef gci_methods[] = { { "_GreedyCoverInstance_get_leftovers", (PyCFunction)_GreedyCoverInstance_get_leftovers, METH_VARARGS | METH_KEYWORDS, "" }, { "_GreedyCoverInstance_get_multisets_incomplete_cover", (PyCFunction)_GreedyCoverInstance_get_multisets_incomplete_cover, METH_VARARGS | METH_KEYWORDS, "" }, { "_GreedyCoverInstance_add_multiset", (PyCFunction)_GreedyCoverInstance_add_multiset, METH_VARARGS | METH_KEYWORDS, "" }, + { "_GreedyCoverInstance_delete_multiset", (PyCFunction)_GreedyCoverInstance_delete_multiset, METH_VARARGS | METH_KEYWORDS, "" }, { "_GreedyCoverInstance_cover", (PyCFunction)_GreedyCoverInstance_cover, METH_VARARGS | METH_KEYWORDS, "" }, { "_GreedyCoverInstance_solution", (PyCFunction)_GreedyCoverInstance_solution, METH_VARARGS | METH_KEYWORDS, "" }, { "_GreedyCoverInstance__coverage_until", (PyCFunction)_GreedyCoverInstance__coverage_until, METH_VARARGS | METH_KEYWORDS, "" }, diff --git a/src/multiset_multicover/BaseSet.cpp b/src/multiset_multicover/BaseSet.cpp index 26a8d2f..c8f6929 100644 --- a/src/multiset_multicover/BaseSet.cpp +++ b/src/multiset_multicover/BaseSet.cpp @@ -11,6 +11,29 @@ BaseSet::BaseSet(const vector& elements) throw Exception("Cannot accept negative elements."); } +BaseSet::BaseSet(const BaseSet& other) + : _n_elements(other._n_elements) +{ + this->_elements.resize(other._n_elements); + std::copy(other._elements.begin(), other._elements.end(), this->_elements.begin()); +#ifdef DEBUG + cout << "Copying " << &other << " to " << this << endl; +#endif +} + +BaseSet& BaseSet::operator=(const BaseSet& other) +{ + if (this != &other) { + this->_n_elements = other._n_elements; + this->_elements.resize(other._n_elements); + std::copy(other._elements.begin(), other._elements.end(), this->_elements.begin()); + } + return *this; +#ifdef DEBUG + cout << "Assigning " << &other << " to " << this << endl; +#endif +} + const size_t& BaseSet::operator[](size_t index) const { if (index >= this->_n_elements) diff --git a/src/multiset_multicover/GreedyCover.cpp b/src/multiset_multicover/GreedyCover.cpp index c5886cc..ba0768e 100644 --- a/src/multiset_multicover/GreedyCover.cpp +++ b/src/multiset_multicover/GreedyCover.cpp @@ -57,14 +57,20 @@ void GreedyCoverInstance::add_multiset(const vector& elements) { this->__check_elements(elements); this->_multisets.emplace_back(elements); // Implicit conversion taking place - this->__update_max_coverage(this->_multisets[this->size() - 1]); // Use last multiset to update coverage + this->__increase_max_coverage(this->_multisets[this->size() - 1]); // Use last multiset to update coverage } void GreedyCoverInstance::add_multiset(const vector& elements, const vector& mult) { this->__check_elements(elements); this->_multisets.emplace_back(elements, mult); // Implicit conversion taking place - this->__update_max_coverage(this->_multisets[this->size() - 1]); + this->__increase_max_coverage(this->_multisets[this->size() - 1]); +} + +void GreedyCoverInstance::delete_multiset(size_t index) +{ + this->__decrease_max_coverage(index); + this->_multisets.erase(this->_multisets.begin() + index); } vector GreedyCoverInstance::__cover() @@ -90,6 +96,7 @@ vector GreedyCoverInstance::__cover() } this->solution.push_back(*ut); this->__update_leftovers(this->_multisets[*ut]); + this->_coverage_until.push_back(this->__current_coverage()); this->_remaining_msets.erase(ut); } @@ -131,12 +138,20 @@ vector GreedyCoverInstance::cover(const vector& coverage, size_t return this->__cover(); } -void GreedyCoverInstance::__update_max_coverage(const MultiSet& mset) +void GreedyCoverInstance::__increase_max_coverage(const MultiSet& mset) { for (size_t i = 0; i < mset.size(); ++i) this->_max_coverage[mset[i].first] += mset[i].second; } +void GreedyCoverInstance::__decrease_max_coverage(size_t index) +{ + if (index >= this->size()) + throw Exception("Index out of bound."); + for (size_t i = 0; i < this->_multisets[index].size(); ++i) + this->_max_coverage[this->_multisets[index][i].first] -= this->_multisets[index][i].second; +} + void GreedyCoverInstance::__init_leftovers() { this->_leftovers.resize(this->_n_elements); @@ -212,4 +227,18 @@ bool GreedyCoverInstance::__stop() const return true; } return false; +} + +size_t GreedyCoverInstance::__current_coverage() const +{ + size_t cc = SIZE_MAX; + + if (!this->_exclusive) + for (size_t i = 0; i < this->_n_elements; ++i) + // No issue with minus since leftovers are always <= + cc = std::min(std::min(this->_coverage_all, this->_max_coverage[i]) - this->_leftovers[i], cc); + else + for (size_t i = 0; i < this->_n_elements; ++i) + cc = std::min(std::min(this->_coverage_idx[i], this->_max_coverage[i]) - this->_leftovers[i], cc); + return cc; } \ No newline at end of file diff --git a/src/multiset_multicover/python_interface.cpp b/src/multiset_multicover/python_interface.cpp index b913b23..09af4d4 100644 --- a/src/multiset_multicover/python_interface.cpp +++ b/src/multiset_multicover/python_interface.cpp @@ -52,8 +52,8 @@ vector create_size_t_vector_from_list(PyObject* py_list) vector v(n); for (size_t i = 0; i < n; ++i) { PyObject* py_item = PyList_GetItem(py_list, i); - if (PyNumber_Check(py_item)) { - size_t num = PyLong_AsSize_t(PyNumber_Long(py_item)); + if (PyLong_Check(py_item)) { + size_t num = PyLong_AsSize_t(py_item); v[i] = num; } else { throw Exception("Non numeric value found."); @@ -72,10 +72,16 @@ PyObject* _new_GreedyCoverInstance(PyObject* self, PyObject* args, PyObject* key if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|O", (char**)kwlist, &py_n_elements)) return NULL; - size_t n_elements = PyLong_AsSize_t(PyNumber_Long(py_n_elements)); - GreedyCoverInstance* gci = create_greedy_cover_instance(n_elements); - PyObject* py_gci = capsule_GreedyCoverInstance(gci); - return py_gci; + try { + size_t n_elements = PyLong_AsSize_t(PyNumber_Long(py_n_elements)); + GreedyCoverInstance* gci = create_greedy_cover_instance(n_elements); + PyObject* py_gci = capsule_GreedyCoverInstance(gci); + return py_gci; + } catch (std::exception const& e) { + string s = "Could not construct greedy cover instance: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } PyObject* _GreedyCoverInstance_size(PyObject* self, PyObject* args, PyObject* keywds) @@ -85,8 +91,14 @@ PyObject* _GreedyCoverInstance_size(PyObject* self, PyObject* args, PyObject* ke if (!PyArg_ParseTupleAndKeywords(args, keywds, "O", (char**)kwlist, &py_gci)) return NULL; - GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); - return PyLong_FromSize_t(gci->size()); + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + return PyLong_FromSize_t(gci->size()); + } catch (std::exception const& e) { + string s = "Could not get instance size: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } PyObject* _GreedyCoverInstance_n_elements(PyObject* self, PyObject* args, PyObject* keywds) @@ -95,9 +107,14 @@ PyObject* _GreedyCoverInstance_n_elements(PyObject* self, PyObject* args, PyObje static const char* kwlist[] = { "gci", NULL }; if (!PyArg_ParseTupleAndKeywords(args, keywds, "O", (char**)kwlist, &py_gci)) return NULL; - - GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); - return PyLong_FromSize_t(gci->n_elements()); + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + return PyLong_FromSize_t(gci->n_elements()); + } catch (std::exception const& e) { + string s = "Could not get instance n elements: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } PyObject* _GreedyCoverInstance_get_max_coverage(PyObject* self, PyObject* args, PyObject* keywds) @@ -107,8 +124,14 @@ PyObject* _GreedyCoverInstance_get_max_coverage(PyObject* self, PyObject* args, if (!PyArg_ParseTupleAndKeywords(args, keywds, "O", (char**)kwlist, &py_gci)) return NULL; - GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); - return create_list_from_size_t_vector(gci->get_max_coverage()); + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + return create_list_from_size_t_vector(gci->get_max_coverage()); + } catch (std::exception const& e) { + string s = "Could not get instance max coverage: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } PyObject* _GreedyCoverInstance_get_leftovers(PyObject* self, PyObject* args, PyObject* keywds) @@ -118,8 +141,14 @@ PyObject* _GreedyCoverInstance_get_leftovers(PyObject* self, PyObject* args, PyO if (!PyArg_ParseTupleAndKeywords(args, keywds, "O", (char**)kwlist, &py_gci)) return NULL; - GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); - return create_list_from_size_t_vector(gci->get_leftovers()); + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + return create_list_from_size_t_vector(gci->get_leftovers()); + } catch (std::exception const& e) { + string s = "Could not get instance leftovers: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } PyObject* _GreedyCoverInstance_get_multisets_incomplete_cover(PyObject* self, PyObject* args, PyObject* keywds) @@ -129,8 +158,14 @@ PyObject* _GreedyCoverInstance_get_multisets_incomplete_cover(PyObject* self, Py if (!PyArg_ParseTupleAndKeywords(args, keywds, "O", (char**)kwlist, &py_gci)) return NULL; - GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); - return create_list_from_size_t_vector(gci->get_multisets_incomplete_cover()); + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + return create_list_from_size_t_vector(gci->get_multisets_incomplete_cover()); + } catch (std::exception const& e) { + string s = "Could not get instance multisets with incomplete cover: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } PyObject* _GreedyCoverInstance_add_multiset(PyObject* self, PyObject* args, PyObject* keywds) @@ -142,14 +177,44 @@ PyObject* _GreedyCoverInstance_add_multiset(PyObject* self, PyObject* args, PyOb if (!PyArg_ParseTupleAndKeywords(args, keywds, "OO|O", (char**)kwlist, &py_gci, &py_elements, &py_mult)) return NULL; - GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); - if (py_mult == NULL || py_mult == Py_None) - gci->add_multiset(create_size_t_vector_from_list(py_elements)); - else - gci->add_multiset(create_size_t_vector_from_list(py_elements), create_size_t_vector_from_list(py_mult)); + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + if (py_mult == NULL || py_mult == Py_None) + gci->add_multiset(create_size_t_vector_from_list(py_elements)); + else + gci->add_multiset(create_size_t_vector_from_list(py_elements), create_size_t_vector_from_list(py_mult)); + + Py_INCREF(Py_None); + return Py_None; + } catch (std::exception const& e) { + string s = "Could not add multiset: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } +} - Py_INCREF(Py_None); - return Py_None; +PyObject* _GreedyCoverInstance_delete_multiset(PyObject* self, PyObject* args, PyObject* keywds) +{ + PyObject* py_gci = NULL; + PyObject* py_index = NULL; + static const char* kwlist[] = { "gci", "index", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "OO", (char**)kwlist, &py_gci, &py_index)) + return NULL; + + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + if (PyLong_Check(py_index) && PyIndex_Check(py_index)) + gci->delete_multiset(PyLong_AsSize_t(py_index)); + else + throw Exception("Non numeric value found."); + + Py_INCREF(Py_None); + return Py_None; + } catch (std::exception const& e) { + string s = "Could not delete multiset: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } PyObject* _GreedyCoverInstance_cover(PyObject* self, PyObject* args, PyObject* keywds) @@ -161,24 +226,34 @@ PyObject* _GreedyCoverInstance_cover(PyObject* self, PyObject* args, PyObject* k if (!PyArg_ParseTupleAndKeywords(args, keywds, "OO|O", (char**)kwlist, &py_gci, &py_coverage, &py_max_iters)) return NULL; - GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); - vector solution; - - size_t max_iters; - if (py_max_iters == NULL || py_max_iters == Py_None) - max_iters = 0; - else - max_iters = PyLong_AsSize_t(PyNumber_Long(py_max_iters)); - - if (PyNumber_Check(py_coverage)) { - size_t coverage = PyLong_AsSize_t(PyNumber_Long(py_coverage)); - solution = gci->cover(coverage, max_iters); - } else { - auto coverage = create_size_t_vector_from_list(py_coverage); - solution = gci->cover(coverage, max_iters); - } + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + vector solution; + + size_t max_iters; + if (py_max_iters == NULL || py_max_iters == Py_None) + max_iters = 0; + else { + if (PyLong_Check(py_max_iters)) + max_iters = PyLong_AsSize_t(py_max_iters); + else + throw Exception("Non integer number found."); + } - return create_list_from_size_t_vector(solution); + if (PyLong_Check(py_coverage)) { + size_t coverage = PyLong_AsSize_t(py_coverage); + solution = gci->cover(coverage, max_iters); + } else { + auto coverage = create_size_t_vector_from_list(py_coverage); + solution = gci->cover(coverage, max_iters); + } + + return create_list_from_size_t_vector(solution); + } catch (std::exception const& e) { + string s = "Could not run the cover algorithm: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } PyObject* _GreedyCoverInstance_solution(PyObject* self, PyObject* args, PyObject* keywds) @@ -188,8 +263,14 @@ PyObject* _GreedyCoverInstance_solution(PyObject* self, PyObject* args, PyObject if (!PyArg_ParseTupleAndKeywords(args, keywds, "O", (char**)kwlist, &py_gci)) return NULL; - GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); - return create_list_from_size_t_vector(gci->solution); + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + return create_list_from_size_t_vector(gci->solution); + } catch (std::exception const& e) { + string s = "Could not get solution: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } PyObject* _GreedyCoverInstance__coverage_until(PyObject* self, PyObject* args, PyObject* keywds) @@ -199,8 +280,14 @@ PyObject* _GreedyCoverInstance__coverage_until(PyObject* self, PyObject* args, P if (!PyArg_ParseTupleAndKeywords(args, keywds, "O", (char**)kwlist, &py_gci)) return NULL; - GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); - return create_list_from_size_t_vector(gci->_coverage_until); + try { + GreedyCoverInstance* gci = decapsule_GreedyCoverInstance(py_gci); + return create_list_from_size_t_vector(gci->_coverage_until); + } catch (std::exception const& e) { + string s = "Could not get step coverage: " + string(e.what()); + PyErr_SetString(PyExc_BaseException, s.c_str()); + return NULL; + } } #ifdef __cplusplus } diff --git a/src/multiset_multicover/python_source.py b/src/multiset_multicover/python_source.py index 76ffc20..e03257b 100644 --- a/src/multiset_multicover/python_source.py +++ b/src/multiset_multicover/python_source.py @@ -28,7 +28,7 @@ def __init__(self, n_elements): The object can be initialized by specifying the number of elements. All elements are assumed to be in range [0, n_elements-1] and adding any elements that extend this range will raise an error. - If your data is not in this range, or it does not consist of numerical + If your data is not in this range, or if it does not consist of numerical values, one can always encode it by using, for example, sklearn.preprocessing.LabelEncoder. @@ -37,6 +37,7 @@ def __init__(self, n_elements): n_elements: int Number of elements. """ + n_elements = int(n_elements) if n_elements < 1: raise ValueError("Cannot have less than 1 element.") @@ -58,21 +59,21 @@ def n_elements(self): return _c_mm._GreedyCoverInstance_n_elements(self._gci) @property - def _max_coverage(self): + def max_coverage_(self): """ The total multiplicity of each element across all multisets. """ return _c_mm._GreedyCoverInstance_get_max_coverage(self._gci) @property - def _leftovers(self): + def leftovers_(self): """ Will only return a value if coverage has been run. """ return _c_mm._GreedyCoverInstance_get_leftovers(self._gci) @property - def _multisets_incomplete_cover(self): + def multisets_incomplete_cover_(self): """ List of multisets for which the desired coverage cannot be achieved. Will only return a value if coverage has been run. @@ -95,6 +96,8 @@ def add_multiset(self, elements, multiplicity=None): """ if min(elements) < 0: raise ValueError("Cannot accept negative elements.") + if max(elements) >= self._n_elements: + raise ValueError("Found value greater than n_elements.") if multiplicity is not None and min(multiplicity) <= 0: raise ValueError("Can only accept positive multiplicities.") if multiplicity is not None and len(multiplicity) != len(elements): @@ -103,6 +106,14 @@ def add_multiset(self, elements, multiplicity=None): _c_mm._GreedyCoverInstance_add_multiset( self._gci, elements, multiplicity) + def delete_multiset(self, index): + """ + Removes the multiset given by index. + """ + if index >= self.size or index < 0: + raise ValueError("Index out of bound.") + _c_mm._GreedyCoverInstance_delete_multiset(self._gci, index) + def cover(self, coverage, max_iters=0): """ Runs the greedy cover algorithm. The specified coverage can be a single @@ -134,3 +145,11 @@ def solution(self): Returns the solution indices. """ return _c_mm._GreedyCoverInstance_solution(self._gci) + + @property + def coverage_until_(self): + """ + Returns the coverage factor for each selected element. Has the same + length as solution. + """ + return _c_mm._GreedyCoverInstance__coverage_until(self._gci) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_interface.py b/tests/test_interface.py new file mode 100644 index 0000000..21e2a97 --- /dev/null +++ b/tests/test_interface.py @@ -0,0 +1,73 @@ +import unittest +import multiset_multicover as mm +import numpy as np +from numpy.testing import assert_equal + + +class test_interface(unittest.TestCase): + def test1(self): + gci = mm.GreedyCoverInstance(4) + gci.add_multiset([0, 1], [2, 1]) + gci.add_multiset([0, 1, 2, 3], [1, 3, 1, 1]) + gci.add_multiset([2, 1, 0], [3, 1, 5]) + gci.add_multiset([0, 1, 2], [6, 3, 1]) + gci.add_multiset([2, 3], [2, 1]) + + assert gci.size == 5 + assert gci.n_elements == 4 + assert_equal(gci.max_coverage_, [14, 8, 7, 2]) + solution = gci.cover(2) + assert_equal(solution, [1, 2, 4]) + coverage_until = gci.coverage_until_ + assert_equal(coverage_until, [1, 1, 2]) + + _ = gci.cover(3) + multisets_incomplete_cover = gci.multisets_incomplete_cover_ + assert_equal(multisets_incomplete_cover, [3]) + + def test2(self): + gci = mm.GreedyCoverInstance(4) + gci.add_multiset([0, 1], [2, 1]) + gci.add_multiset([0, 1, 2, 3], [1, 3, 1, 1]) + gci.add_multiset([2, 1, 0], [3, 1, 5]) + gci.add_multiset([2, 3], [2, 1]) + gci.add_multiset([0, 1, 2], [6, 3, 2]) + + solution = gci.cover(2, max_iters=1) + assert_equal(solution, [4]) + leftovers = gci.leftovers_ + assert_equal(leftovers, [0, 0, 0, 2]) + + gci.delete_multiset(1) + solution = gci.cover(2, max_iters=1) + assert_equal(solution, [3]) + leftovers = gci.leftovers_ + assert_equal(leftovers, [0, 0, 0, 1]) + + try: + gci.cover(23151324513512345134513254135134531) + assert False + except: + pass + + try: + gci.cover("foo") + assert False + except: + pass + + try: + gci.cover(234.1345) + assert False + except: + pass + + try: + gci.cover([234.123, 2]) + assert False + except: + pass + + +if __name__ == "__main__": + unittest.main()