[lint] Add lint and tests.
This commit is contained in:
parent
e42e97064a
commit
800de70a43
1
.dir-locals.el
Normal file
1
.dir-locals.el
Normal file
@ -0,0 +1 @@
|
||||
((python-mode . ((flycheck-flake8rc . "setup.cfg"))))
|
14
.gitignore
vendored
14
.gitignore
vendored
@ -1,3 +1,13 @@
|
||||
# Editor files
|
||||
*~
|
||||
*.pyc
|
||||
**/__pycache__
|
||||
|
||||
# Documentation build
|
||||
.tox
|
||||
doc/_build
|
||||
|
||||
# Python files
|
||||
__pycache__
|
||||
.ipynb_checkpoints
|
||||
.mypy_cache
|
||||
**/*.egg-info
|
||||
**/.eggs
|
||||
|
42
setup.cfg
Normal file
42
setup.cfg
Normal file
@ -0,0 +1,42 @@
|
||||
[flake8]
|
||||
# Use black line length:
|
||||
max-line-length = 88
|
||||
extend-ignore =
|
||||
# See https://github.com/PyCQA/pycodestyle/issues/373
|
||||
E203,
|
||||
|
||||
[mypy]
|
||||
warn_return_any = True
|
||||
warn_unused_configs = True
|
||||
|
||||
[mypy-pytest.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-IPython.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[tox]
|
||||
envlist = py35,py36,py37,py36-lint
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
3.5: py35
|
||||
3.6: py36, py36-lint
|
||||
3.7: py37
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
pytest
|
||||
commands =
|
||||
pytest tests
|
||||
|
||||
[testenv:py36-lint]
|
||||
deps =
|
||||
black
|
||||
flake8
|
||||
flake8-black
|
||||
mypy
|
||||
commands =
|
||||
black --check --diff setup.py simplex tests
|
||||
flake8 simplex tests
|
||||
mypy simplex tests
|
18
setup.py
18
setup.py
@ -2,23 +2,29 @@
|
||||
|
||||
import setuptools
|
||||
|
||||
with open('README.md', 'r') as fh:
|
||||
with open("README.md", "r") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
install_requires = []
|
||||
test_requires = ["pytest", "mypy", "black", "flake8", "flake8-black"]
|
||||
|
||||
setuptools.setup(
|
||||
name="simplex", # Replace with your own username
|
||||
version="0.0.1",
|
||||
author='Mikaël Capelle',
|
||||
author_email='capelle.mikael@gmail.com',
|
||||
description='Implementation of a simplex dictionary in python',
|
||||
author="Mikaël Capelle",
|
||||
author_email="capelle.mikael@gmail.com",
|
||||
description="Implementation of a simplex dictionary in python",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/mikael.capelle/simplex",
|
||||
url="https://gitea.typename.fr/mikael.capelle/simplex",
|
||||
packages=setuptools.find_packages(),
|
||||
install_requires=install_requires,
|
||||
test_requires=test_requires,
|
||||
extras_require={"test": test_requires},
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
python_requires='>=3.5',
|
||||
python_requires=">=3.5",
|
||||
)
|
||||
|
@ -1,29 +1,48 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import typing
|
||||
|
||||
V = typing.TypeVar("V")
|
||||
T = typing.TypeVar("T")
|
||||
|
||||
|
||||
class magic_dictionary(typing.Dict[V, T]):
|
||||
|
||||
class magic_dictionary(dict):
|
||||
"""
|
||||
A magic dictionary is a dictionary that:
|
||||
- May contain a default value, much like collections.defaultdict.
|
||||
- Raise custom errors when the given key does not match a given
|
||||
predicate.
|
||||
- Can convert values using a specific functor.
|
||||
|
||||
Magic dictionary can be constructed the same way standard python dictionary
|
||||
are, except that the constructor takes three extra named parameters.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, key_predicate=None, value_converter=None, default_factory=None, **kwargs):
|
||||
""" Create a new magic dictionary from the given arguments. Magic dictionary can be constructed
|
||||
in the same way as standard python dictionary, except that the constructor takes three extra
|
||||
parameters.
|
||||
key_predicate: typing.Optional[typing.Callable[[V], typing.Optional[str]]] = None
|
||||
value_converter: typing.Optional[typing.Callable[[typing.Any], T]] = None
|
||||
default_factory: typing.Optional[typing.Callable[[], T]] = None
|
||||
|
||||
Parameters:
|
||||
- key_predicate Predicate to apply on new key, which should return None if the key is valid,
|
||||
or a message indicating why the key is invalid otherwize. The predicates is not applied on
|
||||
the initial keys of the dictionary (from args or kwargs). Can be None.
|
||||
- value_converter Functor to apply to values in this dictionary, when set by the index operator.
|
||||
This functor is also applied to all initial values of the dictionary. Can be None.
|
||||
- default_facotry Default factory to use to create object when the key is not set. Can be None.
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
key_predicate: typing.Callable[[V], typing.Optional[str]] = None,
|
||||
value_converter: typing.Callable[[typing.Any], T] = None,
|
||||
default_factory: typing.Callable[[], T] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
key_predicate: Predicate to apply on new key, which should return None if
|
||||
the key is valid, or a message indicating why the key is invalid
|
||||
otherwize. The predicates is not applied on the initial keys of the
|
||||
dictionary (from args or kwargs). Can be None.
|
||||
value_converter: Functor to apply to values in this dictionary, when set
|
||||
by the index operator. This functor is also applied to all initial
|
||||
values of the dictionary. Can be None.
|
||||
default_facotry: Default factory to use to create object when the key is
|
||||
not set. Can be None.
|
||||
"""
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.key_predicate = key_predicate
|
||||
@ -51,7 +70,7 @@ class magic_dictionary(dict):
|
||||
for k in self:
|
||||
super().__setitem__(k, self.value_converter(self[k]))
|
||||
|
||||
def __getitem__(self, key):
|
||||
def __getitem__(self, key: V) -> T:
|
||||
|
||||
# If the key does not exists:
|
||||
if key not in self:
|
||||
@ -59,11 +78,10 @@ class magic_dictionary(dict):
|
||||
if self.default_factory is not None:
|
||||
self.__setitem__(key, self.default_factory())
|
||||
|
||||
|
||||
# Return the key:
|
||||
return super().__getitem__(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
def __setitem__(self, key: V, value: typing.Any):
|
||||
# If the key predicate is not empty, we check the key:
|
||||
if self.key_predicate is not None:
|
||||
check = self.key_predicate(key)
|
||||
@ -77,8 +95,12 @@ class magic_dictionary(dict):
|
||||
super().__setitem__(key, value)
|
||||
|
||||
def update(self, *args, **kargs):
|
||||
super().update(magic_dictionary(
|
||||
*args, **kargs,
|
||||
value_converter=self.value_converter,
|
||||
key_predicate=self.key_predicate,
|
||||
default_factory=self.default_factory))
|
||||
super().update(
|
||||
magic_dictionary(
|
||||
*args,
|
||||
**kargs,
|
||||
value_converter=self.value_converter,
|
||||
key_predicate=self.key_predicate,
|
||||
default_factory=self.default_factory
|
||||
)
|
||||
)
|
||||
|
@ -1,17 +1,27 @@
|
||||
from IPython.display import display, Math
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import typing
|
||||
|
||||
from fractions import Fraction
|
||||
|
||||
from .magic_dictionary import magic_dictionary
|
||||
|
||||
# Type that can be converted by convert_value:
|
||||
convertable_type = typing.Union[str, int, Fraction]
|
||||
|
||||
def convert_value(value):
|
||||
|
||||
def convert_value(value: convertable_type) -> Fraction:
|
||||
if type(value) is float:
|
||||
# For float value, we don't handle float:
|
||||
raise TypeError('Cannot set value as float, use fractions.Fraction instead.')
|
||||
raise TypeError("Cannot set value as float, use fractions.Fraction instead.")
|
||||
return Fraction(value)
|
||||
|
||||
|
||||
class simplex_dictionary:
|
||||
# Type of variables in a simplex_dictionary:
|
||||
V = typing.TypeVar("V")
|
||||
|
||||
|
||||
class simplex_dictionary(typing.Generic[V]):
|
||||
|
||||
""" Class representing a dictionary for the simplex algorithm. The class contains
|
||||
multiple members representing the elements of a dictionary.
|
||||
@ -28,13 +38,14 @@ class simplex_dictionary:
|
||||
The magic member ".variables" can also be used to access the list of all
|
||||
variables.
|
||||
|
||||
The various arrays of coefficients or values (b, a, c) are indexed by their respective
|
||||
variables, which must be in B or N.
|
||||
The various arrays of coefficients or values (b, a, c) are indexed by their
|
||||
respective variables, which must be in B or N.
|
||||
|
||||
All values are converted to fractions.Fraction object in order to maintain consistency
|
||||
in the dictionary.
|
||||
All values are converted to fractions.Fraction object in order to maintain
|
||||
consistency in the dictionary.
|
||||
|
||||
Examples:
|
||||
```
|
||||
# Create a simplex dictionary with two basic and two non-basic variables:
|
||||
>>> sdict = simplex_dictionary(B=['x_1', 'x_2'], N=['x_3', 'x_4'])
|
||||
|
||||
@ -43,114 +54,181 @@ class simplex_dictionary:
|
||||
|
||||
# Set the coefficient of x_3 in x_1:
|
||||
>>> sdict.a['x_1']['x_3'] = 12
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, B, N):
|
||||
""" Create a new simplex dictionary with the given basic and non-basic variables.
|
||||
_a: magic_dictionary[V, magic_dictionary[V, Fraction]]
|
||||
_b: magic_dictionary[V, Fraction]
|
||||
_c: magic_dictionary[V, Fraction]
|
||||
_z: Fraction
|
||||
|
||||
Parameters:
|
||||
- B The list of basic variables.
|
||||
- N The list of non-basic variables.
|
||||
def __init__(self, B: typing.Iterable[V], N: typing.Iterable[V]):
|
||||
"""
|
||||
self.__B = tuple(B)
|
||||
self.__N = tuple(N)
|
||||
self.b = {}
|
||||
self.a = {}
|
||||
self.c = {}
|
||||
self.z = 0
|
||||
Args:
|
||||
B The list of basic variables.
|
||||
N The list of non-basic variables.
|
||||
"""
|
||||
self.__B = list(B)
|
||||
self.__N = list(N)
|
||||
self.b = {} # type: ignore
|
||||
self.a = {} # type: ignore
|
||||
self.c = {} # type: ignore
|
||||
self.z = Fraction(0)
|
||||
|
||||
def _check_basic(self, key):
|
||||
def _check_basic(self, key: V) -> typing.Optional[str]:
|
||||
""" Check if the given key is a basic variable, returning None if it is,
|
||||
and a custom exception string if not. Suitable for magic_dictionary use. """
|
||||
if key not in self.B:
|
||||
return '{} is not a basic variable.'.format(key)
|
||||
return "{} is not a basic variable.".format(key)
|
||||
return None
|
||||
|
||||
def _check_non_basic(self, key):
|
||||
def _check_non_basic(self, key: V) -> typing.Optional[str]:
|
||||
""" Check if the given key is a non-basic variable, returning None if it is,
|
||||
and a custom exception string if not. Suitable for magic_dictionary use. """
|
||||
if key not in self.N:
|
||||
return '{} is not a non-basic variable.'.format(key)
|
||||
return "{} is not a non-basic variable.".format(key)
|
||||
return None
|
||||
|
||||
@property
|
||||
def variables(self): return sorted(self.B + self.N)
|
||||
def variables(self) -> typing.List[V]:
|
||||
return sorted(self.B + self.N)
|
||||
|
||||
@property
|
||||
def B(self): return self.__B
|
||||
def B(self) -> typing.List[V]:
|
||||
return self.__B
|
||||
|
||||
@property
|
||||
def N(self): return self.__N
|
||||
def N(self) -> typing.List[V]:
|
||||
return self.__N
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
@property
|
||||
def a(self) -> magic_dictionary[V, magic_dictionary[V, Fraction]]:
|
||||
return self._a
|
||||
|
||||
# Base and non-basic:
|
||||
if key == 'b':
|
||||
value = magic_dictionary(
|
||||
value,
|
||||
key_predicate=self._check_basic,
|
||||
value_converter=convert_value)
|
||||
elif key == 'c':
|
||||
value = magic_dictionary(
|
||||
@a.setter
|
||||
def a(
|
||||
self,
|
||||
value: typing.Union[
|
||||
typing.Mapping[V, typing.Mapping[V, convertable_type]],
|
||||
typing.Iterable[typing.Tuple[V, typing.Mapping[V, convertable_type]]],
|
||||
],
|
||||
):
|
||||
self._a = magic_dictionary(
|
||||
value,
|
||||
key_predicate=self._check_basic,
|
||||
value_converter=lambda value: magic_dictionary(
|
||||
value,
|
||||
key_predicate=self._check_non_basic,
|
||||
value_converter=convert_value)
|
||||
elif key == 'a':
|
||||
value = magic_dictionary(
|
||||
value,
|
||||
key_predicate=self._check_basic,
|
||||
value_converter=lambda value: magic_dictionary(
|
||||
value, key_predicate=self._check_non_basic,
|
||||
value_converter=convert_value),
|
||||
default_factory=lambda: magic_dictionary(
|
||||
key_predicate=self._check_non_basic,
|
||||
value_converter=convert_value))
|
||||
elif key == 'z':
|
||||
value = convert_value(value)
|
||||
value_converter=convert_value,
|
||||
),
|
||||
default_factory=lambda: magic_dictionary(
|
||||
key_predicate=self._check_non_basic, value_converter=convert_value
|
||||
),
|
||||
)
|
||||
|
||||
return super().__setattr__(key, value)
|
||||
@property
|
||||
def b(self) -> magic_dictionary[V, Fraction]:
|
||||
return self._b
|
||||
|
||||
def name_latex(self, name):
|
||||
""" Convert the given variable name. """
|
||||
name = str(name)
|
||||
@b.setter
|
||||
def b(
|
||||
self,
|
||||
value: typing.Union[
|
||||
typing.Mapping[V, convertable_type],
|
||||
typing.Iterable[typing.Tuple[V, convertable_type]],
|
||||
],
|
||||
):
|
||||
self._b = magic_dictionary(
|
||||
value, key_predicate=self._check_basic, value_converter=convert_value
|
||||
)
|
||||
|
||||
s = name.split('_')
|
||||
@property
|
||||
def c(self) -> magic_dictionary[V, Fraction]:
|
||||
return self._c
|
||||
|
||||
@c.setter
|
||||
def c(
|
||||
self,
|
||||
value: typing.Union[
|
||||
typing.Mapping[V, convertable_type],
|
||||
typing.Iterable[typing.Tuple[V, convertable_type]],
|
||||
],
|
||||
):
|
||||
self._c = magic_dictionary(
|
||||
value, key_predicate=self._check_non_basic, value_converter=convert_value
|
||||
)
|
||||
|
||||
@property
|
||||
def z(self) -> Fraction:
|
||||
return self._z
|
||||
|
||||
@z.setter
|
||||
def z(self, value: convertable_type):
|
||||
self._z = convert_value(value)
|
||||
|
||||
def name_latex(self, name: typing.Any) -> str:
|
||||
""" Convert the given variable name to a clean latex name.
|
||||
|
||||
Args:
|
||||
name: The name of the variable to convert.
|
||||
|
||||
Returns:
|
||||
A latex version of the given name.
|
||||
"""
|
||||
|
||||
sname = str(name)
|
||||
|
||||
s = sname.split("_")
|
||||
|
||||
# We only handle special case:
|
||||
if len(s) == 1 or len(s) > 2:
|
||||
return name
|
||||
return sname
|
||||
|
||||
return s[0] + '_{' + s[1] + '}'
|
||||
return s[0] + "_{" + s[1] + "}"
|
||||
|
||||
def value_latex(self, value):
|
||||
def value_latex(self, value: Fraction) -> str:
|
||||
""" Convert the given fraction to a latex fraction.
|
||||
|
||||
Args:
|
||||
value: The fraction to convert.
|
||||
|
||||
Returns:
|
||||
A valid latex code that is either a number (if the fraction has a
|
||||
denominator of 1), or a latex fraction.
|
||||
"""
|
||||
if value.denominator == 1:
|
||||
return str(value)
|
||||
return r'{}\frac{{{}}}{{{}}}'.format(
|
||||
'-' if value.numerator < 0 else '',
|
||||
abs(value.numerator), value.denominator)
|
||||
return r"{}\frac{{{}}}{{{}}}".format(
|
||||
"-" if value.numerator < 0 else "", abs(value.numerator), value.denominator
|
||||
)
|
||||
|
||||
def display(self, name=None):
|
||||
def display(self, name: str = None):
|
||||
""" Display this simplex dictionary on the standard Jupyter output.
|
||||
|
||||
Parameters:
|
||||
- prefix Name of the dictionary.
|
||||
Args:
|
||||
name: Name of the dictionary.
|
||||
"""
|
||||
d = (r'\begin{{array}}{{r||{}}}'.format(
|
||||
'r|' * (1 + len(self.B)))
|
||||
+ r' & b & ' + ' & '.join(self.name_latex(v) for v in self.N) + r'\\\hline '
|
||||
+ r'\\'.join(
|
||||
'{} & {} &'.format(
|
||||
self.name_latex(b),
|
||||
self.value_latex(self.b[b])
|
||||
)
|
||||
+ ' & '.join(self.value_latex(-self.a[b][n]) for n in self.N)
|
||||
for i, b in enumerate(self.B))
|
||||
+ r'\\\hline\hline '
|
||||
+ r'&'.join(['z', self.value_latex(self.z)] + [self.value_latex(self.c[n])
|
||||
for n in self.N])
|
||||
+ r'\\\hline\end{array}')
|
||||
from IPython.display import display, Math
|
||||
|
||||
d = (
|
||||
r"\begin{{array}}{{r||{}}}".format("r|" * (1 + len(self.B)))
|
||||
+ r" & b & "
|
||||
+ " & ".join(self.name_latex(v) for v in self.N)
|
||||
+ r"\\\hline "
|
||||
+ r"\\".join(
|
||||
"{} & {} &".format(self.name_latex(b), self.value_latex(self.b[b]))
|
||||
+ " & ".join(self.value_latex(-self.a[b][n]) for n in self.N)
|
||||
for i, b in enumerate(self.B)
|
||||
)
|
||||
+ r"\\\hline\hline "
|
||||
+ r"&".join(
|
||||
["z", self.value_latex(self.z)]
|
||||
+ [self.value_latex(self.c[n]) for n in self.N]
|
||||
)
|
||||
+ r"\\\hline\end{array}"
|
||||
)
|
||||
|
||||
if name is not None:
|
||||
d = r'{} = \left.{}\right.'.format(name, d)
|
||||
d = r"{} = \left.{}\right.".format(name, d)
|
||||
|
||||
display(Math(d))
|
||||
|
83
tests/test_magic_dictionary.py
Normal file
83
tests/test_magic_dictionary.py
Normal file
@ -0,0 +1,83 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from simplex.magic_dictionary import magic_dictionary
|
||||
|
||||
|
||||
def test_basic():
|
||||
""" Tests that a non-customized magic_dictionary acts
|
||||
as a standard python dictionary. """
|
||||
|
||||
d = magic_dictionary()
|
||||
assert len(d) == 0
|
||||
|
||||
d["x"] = 1
|
||||
d["y"] = "2"
|
||||
|
||||
assert len(d) == 2
|
||||
assert d["x"] == 1
|
||||
assert d["y"] == "2"
|
||||
|
||||
# Test copy:
|
||||
d2 = magic_dictionary(d)
|
||||
assert len(d2) == 2
|
||||
assert d2["x"] == 1
|
||||
assert d2["y"] == "2"
|
||||
|
||||
# Test construction:
|
||||
d2 = magic_dictionary({"x": 1, "y": "2"})
|
||||
assert len(d2) == 2
|
||||
assert d2["x"] == 1
|
||||
assert d2["y"] == "2"
|
||||
|
||||
# Test construction:
|
||||
d2 = magic_dictionary(x=1, y="2")
|
||||
assert len(d2) == 2
|
||||
assert d2["x"] == 1
|
||||
assert d2["y"] == "2"
|
||||
|
||||
# Test construction:
|
||||
d2 = magic_dictionary((("x", 1), ("y", "2")))
|
||||
assert len(d2) == 2
|
||||
assert d2["x"] == 1
|
||||
assert d2["y"] == "2"
|
||||
|
||||
|
||||
def test_value_converter():
|
||||
""" Tests that conversion is correctly done. """
|
||||
|
||||
# Convert a value to its string representation:
|
||||
def converter(x):
|
||||
return str(x)
|
||||
|
||||
d = magic_dictionary(value_converter=converter)
|
||||
assert len(d) == 0
|
||||
|
||||
d["x"] = 2
|
||||
d["y"] = 3
|
||||
assert len(d) == 2
|
||||
assert d["x"] == "2"
|
||||
assert d["y"] == "3"
|
||||
|
||||
# Construction:
|
||||
d = magic_dictionary(d, value_converter=converter)
|
||||
assert len(d) == 2
|
||||
assert d["x"] == "2"
|
||||
assert d["y"] == "3"
|
||||
|
||||
# Construction:
|
||||
d = magic_dictionary({"x": 2, "y": 3}, value_converter=converter)
|
||||
assert len(d) == 2
|
||||
assert d["x"] == "2"
|
||||
assert d["y"] == "3"
|
||||
|
||||
# Construction:
|
||||
d = magic_dictionary(x=2, y=3, value_converter=converter)
|
||||
assert len(d) == 2
|
||||
assert d["x"] == "2"
|
||||
assert d["y"] == "3"
|
||||
|
||||
# Construction:
|
||||
d = magic_dictionary([("x", 2), ("y", 3)], value_converter=converter)
|
||||
assert len(d) == 2
|
||||
assert d["x"] == "2"
|
||||
assert d["y"] == "3"
|
Loading…
Reference in New Issue
Block a user