[simplex] Initial commit.

This commit is contained in:
Mikaël Capelle 2019-10-30 19:37:08 +01:00
commit f438a8146b
8 changed files with 279 additions and 0 deletions

2
.flake8 Normal file
View File

@ -0,0 +1,2 @@
[flake8]
max-line-length = 100

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*~
*.pyc
**/__pycache__

21
LICENSE Normal file
View 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
View File

@ -0,0 +1,3 @@
# Simplex dictionary
Small package to manipulate a simplex dictionary in python.

24
setup.py Normal file
View 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
View File

@ -0,0 +1,5 @@
# -*- encoding: utf-8 -*-
# flake8: noqa
from .simplex_dictionary import simplex_dictionary

View 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)

View 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))