[simplex] Initial commit.
This commit is contained in:
		
							
								
								
									
										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)) | ||||
		Reference in New Issue
	
	Block a user