[simplex] Initial commit.
This commit is contained in:
commit
f438a8146b
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*~
|
||||||
|
*.pyc
|
||||||
|
**/__pycache__
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019, Mikaël Capelle.
|
||||||
|
|
||||||
|
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.
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Simplex dictionary
|
||||||
|
|
||||||
|
Small package to manipulate a simplex dictionary in python.
|
24
setup.py
Normal file
24
setup.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
with open('README.md', 'r') as fh:
|
||||||
|
long_description = fh.read()
|
||||||
|
|
||||||
|
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',
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
url="https://github.com/mikael.capelle/simplex",
|
||||||
|
packages=setuptools.find_packages(),
|
||||||
|
classifiers=[
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
],
|
||||||
|
python_requires='>=3.5',
|
||||||
|
)
|
5
simplex/__init__.py
Normal file
5
simplex/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
# flake8: noqa
|
||||||
|
|
||||||
|
from .simplex_dictionary import simplex_dictionary
|
77
simplex/magic_dictionary.py
Normal file
77
simplex/magic_dictionary.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.key_predicate = key_predicate
|
||||||
|
self.value_converter = value_converter
|
||||||
|
self.default_factory = default_factory
|
||||||
|
|
||||||
|
# Copy construction:
|
||||||
|
if len(args) >= 1 and isinstance(args[0], magic_dictionary):
|
||||||
|
if key_predicate is None:
|
||||||
|
self.key_predicate = args[0].key_predicate
|
||||||
|
if value_converter is None:
|
||||||
|
self.value_converter = args[0].value_converter
|
||||||
|
if default_factory is None:
|
||||||
|
self.default_factory = args[0].default_factory
|
||||||
|
|
||||||
|
# Check the value:
|
||||||
|
if self.key_predicate is not None:
|
||||||
|
for k in self:
|
||||||
|
check = self.key_predicate(k)
|
||||||
|
if check is not None:
|
||||||
|
raise IndexError(check)
|
||||||
|
|
||||||
|
# Convert the value:
|
||||||
|
if self.value_converter is not None:
|
||||||
|
for k in self:
|
||||||
|
super().__setitem__(k, self.value_converter(self[k]))
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
|
||||||
|
# If the key does not exists:
|
||||||
|
if key not in self:
|
||||||
|
# We try to set it to a new object:
|
||||||
|
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):
|
||||||
|
# If the key predicate is not empty, we check the key:
|
||||||
|
if self.key_predicate is not None:
|
||||||
|
check = self.key_predicate(key)
|
||||||
|
if check is not None:
|
||||||
|
raise KeyError(check)
|
||||||
|
|
||||||
|
# Convert the value:
|
||||||
|
if self.value_converter is not None:
|
||||||
|
value = self.value_converter(value)
|
||||||
|
|
||||||
|
super().__setitem__(key, value)
|
144
simplex/simplex_dictionary.py
Normal file
144
simplex/simplex_dictionary.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
from IPython.display import display, Math
|
||||||
|
from fractions import Fraction
|
||||||
|
|
||||||
|
from .magic_dictionary import magic_dictionary
|
||||||
|
|
||||||
|
|
||||||
|
def convert_value(value):
|
||||||
|
if type(value) is float:
|
||||||
|
# For float value, we don't handle float:
|
||||||
|
raise TypeError('Cannot set value as float, use fractions.Fraction instead.')
|
||||||
|
return Fraction(value)
|
||||||
|
|
||||||
|
|
||||||
|
class simplex_dictionary:
|
||||||
|
|
||||||
|
""" Class representing a dictionary for the simplex algorithm. The class contains
|
||||||
|
multiple members representing the elements of a dictionary.
|
||||||
|
|
||||||
|
Members:
|
||||||
|
- B The list of basic variables (immutable).
|
||||||
|
- N The list of non-basic variables (immutable).
|
||||||
|
- b The current value of the basic variables.
|
||||||
|
- a The current coefficients of the non-basic variables in the expression
|
||||||
|
of the basic variables.
|
||||||
|
- c The current coefficients of the non-basic variables in the objective.
|
||||||
|
- z The current value of the objective.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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'])
|
||||||
|
|
||||||
|
# Set the value of x_1:
|
||||||
|
>>> sdict.b['x_1'] = 4
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- B The list of basic variables.
|
||||||
|
- N The list of non-basic variables.
|
||||||
|
"""
|
||||||
|
self.__B = tuple(B)
|
||||||
|
self.__N = tuple(N)
|
||||||
|
self.b = {}
|
||||||
|
self.a = {}
|
||||||
|
self.c = {}
|
||||||
|
self.z = 0
|
||||||
|
|
||||||
|
def _check_basic(self, key):
|
||||||
|
""" 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 None
|
||||||
|
|
||||||
|
def _check_non_basic(self, key):
|
||||||
|
""" 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 None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def variables(self): return sorted(self.B + self.N)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def B(self): return self.__B
|
||||||
|
|
||||||
|
@property
|
||||||
|
def N(self): return self.__N
|
||||||
|
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
|
||||||
|
# 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(
|
||||||
|
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)
|
||||||
|
|
||||||
|
return super().__setattr__(key, value)
|
||||||
|
|
||||||
|
def value_latex(self, value):
|
||||||
|
if value.denominator == 1:
|
||||||
|
return str(value)
|
||||||
|
return r'{}\frac{{{}}}{{{}}}'.format(
|
||||||
|
'-' if value.numerator < 0 else '',
|
||||||
|
abs(value.numerator), value.denominator)
|
||||||
|
|
||||||
|
def display(self, name=None):
|
||||||
|
""" Display this simplex dictionary on the standard Jupyter output.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- prefix Name of the dictionary.
|
||||||
|
"""
|
||||||
|
d = (r'\begin{{array}}{{r||{}}}'.format(
|
||||||
|
'r|' * (1 + len(self.B)))
|
||||||
|
+ r' & b & ' + ' & '.join(str(v) for v in self.N) + r'\\\hline '
|
||||||
|
+ r'\\'.join(
|
||||||
|
'{} & {} &'.format(
|
||||||
|
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)
|
||||||
|
|
||||||
|
display(Math(d))
|
Loading…
Reference in New Issue
Block a user