[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