├── .gitignore ├── LICENSE ├── PYPI_README.md ├── README.md ├── old ├── build │ └── lib │ │ └── pysmps │ │ ├── __init__.py │ │ └── smps_loader.py ├── dist │ ├── pysmps-1.5.6-py3-none-any.whl │ └── pysmps-1.5.6.tar.gz └── pysmps.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ └── top_level.txt ├── pysmps ├── __init__.py ├── __pycache__ │ └── mps_loader.cpython-38.pyc ├── mps_loader.py ├── smps_loader.py └── test │ ├── __init__.py │ ├── case01 │ ├── case02 │ └── test.py ├── requirements.txt ├── requirements_dev.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | build/* 3 | pysmps.egg-info/* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Julian Märte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PYPI_README.md: -------------------------------------------------------------------------------- 1 | # pysmps 2 | 3 | This is a utility script for parsing MPS and SMPS file formats. It offers two main functions `load_mps` for loading mps files and `load_smps` for loading smps file directory. 4 | 5 | **DISCLAIMER** This parser assumes a certain order of instructions in the MPS file and so a latter instruction colliding with an earlier one counts. In case you find a unnatural behavior in this parser let me know via github. 6 | 7 | ### `read_mps` 8 | 9 | The `read_mps(path)` method takes a `path` variable as input. The contents of the file under `path` are assumed to be in MPS format. 10 | It opens the file and parses the described linear program into an instance of the class `MPS`. Note that `MPS` has always attached a group of the `RHS`, `BOUNDS` and `RANGES` sections. If there are no multiple groups in the file this is irrelevant to the user; in case there are multiple groups in the mps file given you can see the list of groups via the following methods: 11 | 12 | * `MPS.bnd_names()` for a list of the boundary group names 13 | * `MPS.rhs_names()` for a list of the RHS group names 14 | * `MPS.range_names()` for a list of range names. 15 | 16 | In order to obtain the values for different choices of group names you can attach a group via 17 | 18 | * `MPS.attach_bnd(group)` to attach the boundary group with name `group` 19 | * `MPS.attach_rhs(group)` to attach the rhs group with name `group` 20 | * `MPS.attach_range(group)` to attach the range group with name `group` 21 | 22 | After attachment you can modify the linear program for this particular group choice via the methods for modification or obtain the linear program parameters for the choice via the getter functions: 23 | 24 | * `MPS.get_variables()` returns a `dict` mapping a variable name to a `dict` containing the `type` and bounds (`lower`, `upper`) w.r.t. the attached BOUNDS group 25 | * `MPS.get_rhs()` returns a `dict` mapping a **constraint** row name to the RHS value of the row w.r.t. the attached RHS group 26 | * `MPS.get_offsets()` returns a `dict` mapping an **objective** row name to the RHS value (i.e. offset) of the row w.r.t. the attached RHS group 27 | * `MPS.get_ranges()` returns a `dict` mapping a contraint row name to a `dict` containing the `upper` and `lower` deviation of the interval in which the row has to be from its respective RHS value w.r.t. the attached RANGES group 28 | 29 | In order to see the entire linear program (without having to attach groups) it is useful to convert the class instance into a `dict` via the pre-implemented functionality 30 | 31 | ```python 32 | M = read_mps("some/path") 33 | dict(M) 34 | ``` 35 | 36 | This function returns a `dict` with all the information of `M`. This `dict` is a mapping of the following kind: 37 | 38 | ```python 39 | name -> str: Name of program 40 | objective_names -> list of str: Names of the objective rows 41 | bnd_names -> list of str: Names of the different boundary configurations given 42 | rhs_names -> list of str: Names of the different RHS configurations given 43 | range_names -> list of str: Names of range configurations 44 | constraint_names -> list of str: Names of constraint rows 45 | variable_names -> list of str: Names of variables 46 | objectives -> dict: Maps each of objective_names to a dict mapping each of variable_names with non-zero 47 | coefficient for this objective to its respective coefficient 48 | variables -> dict: Maps each of bnd_names to a dict mapping each of variable_names to a dict describing the variable for this particular boundary setting. The dict has keys "type", "lower" and 49 | "upper". 50 | constraints -> dict: Maps each of constraint_names to a dict containing the type of the constraint 51 | ("E", "L", "G") and a dict mapping each of variable_names having non-zero coefficient for 52 | this constraint to its coefficient 53 | rhs -> dict: Maps each of rhs_names to a dict mapping each of of constraint_names to its respective rhs 54 | value 55 | offsets -> dict: Maps each of rhs_names to a dict mapping each of objective_names to their offset in this 56 | particular rhs configuration 57 | ranges -> dict: Maps each of range_names to a dict containing the ranges for this particular range 58 | configuration. This dict maps one of constraint_names to a dict containing "lower" and 59 | "upper" if ranges are given for it 60 | ``` 61 | 62 | 63 | 64 | **NOTE** Currently this code does not support `SOS` tags. However the reader will skip over this section with no errors. The default behavior of this parser is as follows: 65 | 66 | * The default bounds for continuous aswell as integer values are `{lower: 0, upper: math.inf}`. You can change this by calling the `read_mps` function with the additional arguments `c_lower, c_upper, i_lower, i_upper` and the respective values for continuous and integer default bounds. Note that the `i_lower` and `i_upper` bounds are only applied to variables declared in an `INTORG`, `INTEND` block. They are not applied to continuously declared variables which become integral by `LI` or `UI` BOUNDS tags. 67 | * If conflicting BOUNDS, RHS or RANGE values are given the one given last counts. 68 | * The BOUNDS tags have following default bound values: 69 | * The default bounds if none are given are `i_lower, c_lower: 0` and `i_upper, c_upper: math.inf` 70 | * `MI` has a default upper bound of `0`; can be set by the `MI_upper` argument 71 | * `SC` has a default lower bound of `1` if no argument is given; can be set by the `SC_lower` argument 72 | * The general behaviour of BOUNDS tags is that the relevant portions of previous BOUNDS is overwritten; `MI` for example overwrites the previous `lower` bound to `-math.inf` but also the previous `upper` bound to the given upper bound or `MI_default`. Thus the file should set explicit upper bounds either in the `MI` line or after it but never before (similar behavior for `SC`). 73 | * As mentioned above the `LI` and `UI` commands do not apply the integer default bounds `i_lower`, `i_upper`, `LI` and `UI` only change the variable to `Integer` and set the respective bound; the other bound stays as is. 74 | * Variable types can be `Integer`, `Continuous` or `Semi-Continuous` 75 | 76 | `load_smps` 77 | 78 | This function makes use of the `load_mps` function for parsing the .cor file. The SMPS file format consists of three files, a .cor, .tim and .sto file. The .cor file is in MPS format. Further the function expects a parameter `path` to be such that `path + ".cor"` is the core file, `path + ".tim"` the time file and `path + ".sto"` is the stochastic file. 79 | 80 | **NOTE** It *does not* support scenarios or nodes! 81 | 82 | Similar to the `MPS` object the `SMPS` object can be converted into a `dict` containing all information of the object. This `dict` has the same fields as its underlying `MPS` class from the .cor file. The remaining fields are: 83 | 84 | ```python 85 | row_periods -> dict: Maps every period name to a list of str containing the rows belonging to this period 86 | col_periods -> dict: Maps every period name to a list of str containing the cols belonging to this period 87 | distributions -> dict: Maps every period for which distributions are given to a dict mapping every pair 88 | (i.e. tuple) of row and col names to a dict describing the distribution. This dict either 89 | declares the distribution directly if one is given or refers to a block distribution 90 | blocks -> dict: Maps every period to a dict of blocks describing the distribution for each block 91 | ``` 92 | 93 | The same default behavior as in the `read_mps` function hold. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pysmps 2 | Utilities for parsing MPS and SMPS file formats. 3 | ## Installation 4 | You can install this package using PyPI with the command 5 | ```bash 6 | pip install pysmps 7 | ``` 8 | After installation you can import the reader as 9 | ```python 10 | from pysmps import smps_loader as smps 11 | ``` 12 | at the beginning of your python code. For full documentation take a look at the PyPI page, . 13 | 14 | ## TODO 15 | - [ ] Scenario support in sto file parsing. 16 | - [x] Block support in 2-stage problem casting. 17 | -------------------------------------------------------------------------------- /old/build/lib/pysmps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmaerte/pysmps/aacdfc26959a0b267b331860e1fb717d924b3a52/old/build/lib/pysmps/__init__.py -------------------------------------------------------------------------------- /old/build/lib/pysmps/smps_loader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Sep 8 13:28:53 2019 4 | 5 | @author: Julian Märte 6 | """ 7 | 8 | import re 9 | import numpy as np 10 | import math 11 | import copy 12 | 13 | CORE_FILE_ROW_MODE = "ROWS" 14 | CORE_FILE_COL_MODE = "COLUMNS" 15 | CORE_FILE_RHS_MODE = "RHS" 16 | CORE_FILE_BOUNDS_MODE = "BOUNDS" 17 | 18 | CORE_FILE_BOUNDS_MODE_NAME_GIVEN = "BOUNDS_NAME" 19 | CORE_FILE_BOUNDS_MODE_NO_NAME = "BOUNDS_NO_NAME" 20 | CORE_FILE_RHS_MODE_NAME_GIVEN = "RHS_NAME" 21 | CORE_FILE_RHS_MODE_NO_NAME = "RHS_NO_NAME" 22 | 23 | ROW_MODE_OBJ = "N" 24 | 25 | 26 | TIME_FILE_PERIODS_MODE = "PERIODS" 27 | TIME_FILE_PERIODS_MODE_EXPLICIT = "PERIODS_EXPLICIT" 28 | TIME_FILE_PERIODS_MODE_IMPLICIT = "PERIODS_IMPLICIT" 29 | TIME_FILE_ROWS_MODE = "ROWS" 30 | TIME_FILE_COLS_MODE = "COLUMNS" 31 | 32 | 33 | STOCH_FILE_SIMPLE_MODE = "SIMPLE" 34 | STOCH_FILE_CHANCE_MODE = "CHANCE" 35 | STOCH_FILE_SCENARIOS_MODE = "SCENARIOS" 36 | STOCH_FILE_NODES_MODE = "NODES" 37 | STOCH_FILE_INDEP_MODE = "INDEP" 38 | STOCH_FILE_BLOCKS_MODE = "BLOCKS" 39 | STOCH_FILE_DISTRIB_MODE = "DISTRIB" 40 | STOCH_FILE_BLOCKS_BL_MODE = "BL" 41 | 42 | # BLOCKS modes 43 | STOCH_FILE_BLOCKS_DISCRETE_MODE = "BLOCKS_DISCRETE" 44 | STOCH_FILE_BLOCKS_SUB_MODE = "BLOCKS_SUB" 45 | STOCH_FILE_BLOCKS_LINTR_MODE = "BLOCKS_LINTR" 46 | 47 | #INDEP modes 48 | STOCH_FILE_INDEP_DISCRETE_MODE = "INDEP_DISCRETE" 49 | STOCH_FILE_INDEP_DISTRIB_MODE = "INDEP_DISTRIB" 50 | 51 | class Block(object): 52 | 53 | period = "" 54 | probabilities = [] 55 | cases = [] 56 | 57 | def __init__(self, period): 58 | self.period = period 59 | 60 | def add(self, probability): 61 | self.probabilities.append(probability) 62 | if len(self.cases) > 0: 63 | self.cases.append(copy.deepcopy(self.cases[0])) 64 | else: 65 | self.cases.append({"c": {}, "A": {}, "b": {}}) 66 | 67 | def add_case(self, probability, case): 68 | self.probabilities.append(probability) 69 | self.cases.append(case) 70 | 71 | def append_matrix(self, i, j, value): 72 | self.cases[-1]["A"][(i,j)] = value 73 | 74 | def append_obj(self, j, value): 75 | self.cases[-1]["c"][j] = value 76 | 77 | def append_rhs(self, i, rhs, value): 78 | try: 79 | self.cases[-1]["b"][rhs][i] = value 80 | except: 81 | self.cases[-1]["b"][rhs] = {i: value} 82 | 83 | """ 84 | Implements the random variables that are linear transforms of distributions. 85 | places save the locations of the random variables in the LP 86 | matrix has as i-th entry the coefficients of the random variables 87 | variables holds the distributions that are linearly combined as dicts, e.g. 88 | {"type": "normal", "parameters": {"mu": mu, "sigma**2": sigma**2}} 89 | """ 90 | class LinearTransform(object): 91 | _mapping = {} 92 | places = [] 93 | variables = [] 94 | matrix = np.matrix([[]]) 95 | period = "" 96 | 97 | def __init__(self, period): 98 | self.period = period 99 | 100 | def add_location(self, i, j, description): 101 | self._mapping[(i,j)] = len(self.places) 102 | self.places.append(description) 103 | 104 | def add_distribution(self, type_of, name, parameters): 105 | self.variables.append({"type": type_of, "name": name, "parameters": parameters}) 106 | if self.matrix.shape[1] == 0: 107 | matrix = np.zeros((len(self.places),1)) 108 | else: 109 | matrix = np.append((matrix, np.zeros((len(self.places), 1))), axis = 1) 110 | def add_value(self, i, j, value): 111 | self.matrix[self._mapping[(i,j)],-1] = value 112 | 113 | class SubRoutine(object): 114 | subroutine_locations = [] 115 | period = "" 116 | 117 | def __init__(self, period): 118 | self.period = period 119 | 120 | def add(self, location): 121 | self.subroutine_locations.append(location) 122 | 123 | # private 124 | 125 | def _load_time_file(path, name, row_names, col_names): 126 | mode = "" 127 | rows = False 128 | cols = False 129 | explicit = False 130 | periods = [] 131 | row_periods = [""] * len(row_names) 132 | col_periods = [""] * len(col_names) 133 | 134 | with open(path + ".tim", "r") as reader: 135 | for line in reader: 136 | if line.startswith("*"): 137 | continue 138 | line = re.split(" |\t", line) 139 | line = [x.strip() for x in line] 140 | line = list(filter(None, line)) 141 | 142 | if len(line) == 0: 143 | continue 144 | if line[0] == "*": 145 | continue 146 | if line[0] == "ENDATA": 147 | break 148 | if line[0] == "TIME": 149 | assert line[1] == name 150 | elif line[0] == TIME_FILE_PERIODS_MODE: 151 | if len(line) > 1 and line[1] == "EXPLICIT": 152 | mode = TIME_FILE_PERIODS_MODE_EXPLICIT 153 | explicit = True 154 | else: # in case it is blank, IMPLICIT or anything else set it to implicit. 155 | mode = TIME_FILE_PERIODS_MODE_IMPLICIT 156 | elif line[0] == TIME_FILE_ROWS_MODE: 157 | rows = True 158 | mode = TIME_FILE_ROWS_MODE 159 | elif line[0] == TIME_FILE_COLS_MODE: 160 | cols = True 161 | mode = TIME_FILE_COLS_MODE 162 | elif mode == TIME_FILE_PERIODS_MODE_IMPLICIT: 163 | try: 164 | k = periods.index(line[2]) 165 | except: 166 | periods.append(line[2]) 167 | j = col_names.index(line[0]) 168 | i = row_names.index(line[1]) 169 | col_periods[j:] = [line[2]] * (len(col_periods) - j) 170 | row_periods[i:] = [line[2]] * (len(row_periods) - i) 171 | elif mode == TIME_FILE_ROWS_MODE: 172 | try: 173 | i = periods.index(line[1]) 174 | except: 175 | periods.append(line[1]) 176 | row_periods[row_names.index(line[0])] = line[1] 177 | elif mode == TIME_FILE_COLS_MODE: 178 | try: 179 | i = periods.index(line[1]) 180 | except: 181 | periods.append(line[1]) 182 | col_periods[col_names.index(line[0])] = line[1] 183 | if explicit and not (rows and cols): 184 | raise Exception("Time format was explicit but some rows/cols don't have temporal information!") 185 | return periods, row_periods, col_periods 186 | 187 | def _load_stoch_file(path, name, objective_name, row_names, col_names, rhs_names): 188 | mode = "" 189 | last_block = "" 190 | blocks = {} 191 | independent_variables = {} 192 | 193 | is_rv = False 194 | indep_distribution = "" 195 | 196 | 197 | with open(path + ".sto", "r") as reader: 198 | for line in reader: 199 | if line.startswith("*"): 200 | continue 201 | 202 | line = re.split(" |\t", line) 203 | line = [x.strip() for x in line] 204 | line = list(filter(None, line)) 205 | 206 | if len(line) == 0: 207 | continue 208 | if line[0] == "STOCH": 209 | assert line[1] == name 210 | if line[0] == "ENDATA": 211 | break 212 | if line[0] == "*": 213 | continue 214 | if line[0] == STOCH_FILE_BLOCKS_MODE: 215 | if line[1] == "LINTR": 216 | mode = STOCH_FILE_BLOCKS_LINTR_MODE 217 | elif line[1] == "SUB": 218 | mode = STOCH_FILE_BLOCKS_SUB_MODE 219 | elif line[1] == "DISCRETE": 220 | mode = STOCH_FILE_BLOCKS_DISCRETE_MODE 221 | else: 222 | raise Exception("BLOCKS section was initiated without another tag (LINTR, SUB, DISCRETE).") 223 | elif line[0] == STOCH_FILE_INDEP_MODE: 224 | if line[1] == "DISCRETE": 225 | mode = STOCH_FILE_INDEP_DISCRETE_MODE 226 | elif line[1] in ["UNIFORM", "NORMAL", "BETA", "GAMMA", "LOGNORMAL"]: 227 | mode = STOCH_FILE_INDEP_DISTRIB_MODE 228 | indep_distribution = line[1] 229 | # BLOCKS mode handler 230 | elif mode == STOCH_FILE_BLOCKS_DISCRETE_MODE: 231 | if line[0] == STOCH_FILE_BLOCKS_BL_MODE: 232 | if line[1] not in blocks: 233 | blocks[line[1]] = Block(line[2]) 234 | blocks[line[1]].add(float(line[3])) 235 | last_block = line[1] 236 | elif last_block != "": 237 | i, j = _get_indices(row_names, col_names, line) 238 | if i < 0: 239 | blocks[last_block].append_obj(j, float(line[2])) 240 | elif j < 0: 241 | blocks[last_block].append_rhs(i, line[0], float(line[2])) 242 | else: 243 | blocks[last_block].append_matrix(i, j, float(line[2])) 244 | elif mode == STOCH_FILE_BLOCKS_LINTR_MODE: 245 | if line[0] == STOCH_FILE_BLOCKS_BL_MODE: 246 | is_rv = False 247 | if line[1] not in blocks: 248 | blocks[line[1]] = LinearTransform(line[2]) 249 | last_block = line[1] 250 | elif line[0] == "RV": 251 | if line[2] == "NORMAL": 252 | blocks[last_block].add_distribution("N(mu, sigma**2)", line[1], {"mu": float(line[3]), "sigma**2": float(line[5])}) 253 | elif line[2] == "UNIFORM": 254 | blocks[last_block].add_distribution("U(a, b)", line[1], {"a": float(line[3]), "b": float(line[5])}) 255 | elif line[2] == "CONSTANT": 256 | blocks[last_block].add_distribution("CONST. 1", line[1], {}) 257 | else: 258 | raise Exception("Looks like you are using an unknown distribution to this script. For now we only support NORMAL, UNIFORM and CONSTANT distributions.") 259 | is_rv = True 260 | elif not is_rv and last_block != "": 261 | i, j = _get_indices(row_names, col_names, line) 262 | if i < 0: 263 | description = {"type": line[1], "VAR": j} 264 | elif j < 0: 265 | description = {"type": line[0], "ROW": i} 266 | else: 267 | description = {"type": "matrix", "ROW": i, "COL": j} 268 | blocks[last_block].add_location(i, j, description) 269 | elif is_rv: 270 | i, j = _get_indices(row_names, col_names, line) 271 | blocks[last_block].add_value(i, j, float(line[2])) 272 | elif mode == STOCH_FILE_BLOCKS_SUB_MODE: 273 | if line[0] == STOCH_FILE_BLOCKS_BL_MODE: 274 | if line[1] not in blocks: 275 | blocks[line[1]] = SubRoutine(line[2]) 276 | last_block = line[1] 277 | elif last_block != "": 278 | i, j = _get_indices(row_names, col_names, line) 279 | if i < 0: 280 | description = {"type": line[1], "VAR": j} 281 | elif j < 0: 282 | description = {"type": line[0], "ROW": i} 283 | else: 284 | description = {"type": "matrix", "ROW": i, "COL": j} 285 | blocks[last_block].add(description) 286 | # INDEP mode handler 287 | elif mode == STOCH_FILE_INDEP_DISCRETE_MODE: 288 | i, j = _get_indices(row_names, col_names, line) 289 | if (i,j) in independent_variables: 290 | independent_variables[(i,j)]["distrib"].append((float(line[2]), float(line[4]))) 291 | else: 292 | if i < 0: 293 | description = {"type": line[1], "VAR": j} 294 | elif j < 0: 295 | description = {"type": line[0], "ROW": i} 296 | else: 297 | description = {"type": "matrix", "ROW": i, "COL": j} 298 | independent_variables[(i,j)] = {"position": description, "period": line[3], "distrib": [(float(line[2]), float(line[4]))]} 299 | elif mode == STOCH_FILE_INDEP_DISTRIB_MODE: 300 | i, j = _get_indices(row_names, col_names, line) 301 | if (i,j) in independent_variables: 302 | raise Exception("Tried to set independent distribution of an element twice.") 303 | else: 304 | if i < 0: 305 | description = {"type": line[1], "VAR": j} 306 | elif j < 0: 307 | description = {"type": line[0], "ROW": i} 308 | else: 309 | description = {"type": "matrix", "ROW": i, "COL": j} 310 | if indep_distribution == "NORMAL": 311 | independent_variables[(i,j)] = {"position": description, "period": line[3], 312 | "distrib": {"type": "N(mu, sigma**2)", "parameters": {"mu": float(line[2]), "sigma**2": float(line[4])}}} 313 | elif indep_distribution == "UNIFORM": 314 | independent_variables[(i,j)] = {"position": description, "period": line[3], 315 | "distrib": {"type": "U(a, b)", "parameters": {"a": float(line[2]), "b": float(line[4])}}} 316 | elif indep_distribution == "BETA": 317 | independent_variables[(i,j)] = {"position": description, "period": line[3], 318 | "distrib": {"type": "B(p, q)", "parameters": {"p": float(line[2]), "q": float(line[4])}}} 319 | elif indep_distribution == "GAMMA": 320 | independent_variables[(i,j)] = {"position": description, "period": line[3], 321 | "distrib": {"type": "G(p, b)", "parameters": {"p": float(line[2]), "b": float(line[4])}}} 322 | elif indep_distribution == "LOGNORMAL": 323 | independent_variables[(i,j)] = {"position": description, "period": line[3], 324 | "distrib": {"type": "LN(mu, sigma**2)", "parameters": {"mu": float(line[2]), "sigma**2": float(line[4])}}} 325 | return blocks, independent_variables 326 | def _get_indices(row_names, col_names, line): 327 | i = -1 328 | j = -1 329 | try: 330 | j = col_names.index(line[0]) 331 | except: 332 | pass 333 | try: 334 | i = row_names.index(line[1]) 335 | except: 336 | pass 337 | return i, j 338 | # public 339 | def load_mps(path): 340 | mode = "" 341 | name = None 342 | objective_name = None 343 | row_names = [] 344 | types = [] 345 | col_names = [] 346 | col_types = [] 347 | A = np.matrix([[]]) 348 | c = np.array([]) 349 | rhs_names = [] 350 | rhs = {} 351 | bnd_names = [] 352 | bnd = {} 353 | integral_marker = False 354 | 355 | with open(path, "r") as reader: 356 | for line in reader: 357 | if line.startswith("*"): 358 | continue 359 | 360 | line = re.split(" |\t", line) 361 | line = [x.strip() for x in line] 362 | line = list(filter(None, line)) 363 | 364 | if len(line) == 0: 365 | continue 366 | if line[0] == "ENDATA": 367 | break 368 | if line[0] == "*": 369 | continue 370 | if line[0] == "NAME": 371 | name = line[1] 372 | elif line[0] in [CORE_FILE_ROW_MODE, CORE_FILE_COL_MODE]: 373 | mode = line[0] 374 | elif line[0] == CORE_FILE_RHS_MODE and len(line) <= 2: 375 | if len(line) > 1: 376 | rhs_names.append(line[1]) 377 | rhs[line[1]] = np.zeros(len(row_names)) 378 | mode = CORE_FILE_RHS_MODE_NAME_GIVEN 379 | else: 380 | mode = CORE_FILE_RHS_MODE_NO_NAME 381 | elif line[0] == CORE_FILE_BOUNDS_MODE and len(line) <= 2: 382 | if len(line) > 1: 383 | bnd_names.append(line[1]) 384 | bnd[line[1]] = {"LO": np.zeros(len(col_names)), "UP": np.repeat(math.inf, len(col_names))} 385 | mode = CORE_FILE_BOUNDS_MODE_NAME_GIVEN 386 | else: 387 | mode = CORE_FILE_BOUNDS_MODE_NO_NAME 388 | elif mode == CORE_FILE_ROW_MODE: 389 | if line[0] == ROW_MODE_OBJ: 390 | objective_name = line[1] 391 | else: 392 | types.append(line[0]) 393 | row_names.append(line[1]) 394 | elif mode == CORE_FILE_COL_MODE: 395 | if len(line) > 1 and line[1] == "'MARKER'": 396 | if line[2] == "'INTORG'": 397 | integral_marker = True 398 | elif line[2] == "'INTEND'": 399 | integral_marker = False 400 | continue 401 | try: 402 | i = col_names.index(line[0]) 403 | except: 404 | if A.shape[1] == 0: 405 | A = np.zeros((len(row_names), 1)) 406 | else: 407 | A = np.concatenate((A, np.zeros((len(row_names), 1))), axis = 1) 408 | col_names.append(line[0]) 409 | col_types.append(integral_marker * 'integral' + (not integral_marker) * 'continuous') 410 | c = np.append(c, 0) 411 | i = -1 412 | j = 1 413 | while j < len(line) - 1: 414 | if line[j] == objective_name: 415 | c[i] = float(line[j + 1]) 416 | else: 417 | A[row_names.index(line[j]), i] = float(line[j + 1]) 418 | j = j + 2 419 | elif mode == CORE_FILE_RHS_MODE_NAME_GIVEN: 420 | if line[0] != rhs_names[-1]: 421 | raise Exception("Other RHS name was given even though name was set after RHS tag.") 422 | for kk in range((len(line) - 1) // 2): 423 | idx = kk * 2 424 | rhs[line[0]][row_names.index(line[idx+1])] = float(line[idx+2]) 425 | elif mode == CORE_FILE_RHS_MODE_NO_NAME: 426 | try: 427 | i = rhs_names.index(line[0]) 428 | except: 429 | rhs_names.append(line[0]) 430 | rhs[line[0]] = np.zeros(len(row_names)) 431 | i = -1 432 | for kk in range((len(line) - 1) // 2): 433 | idx = kk * 2 434 | rhs[line[0]][row_names.index(line[idx+1])] = float(line[idx+2]) 435 | elif mode == CORE_FILE_BOUNDS_MODE_NAME_GIVEN: 436 | if line[1] != bnd_names[-1]: 437 | raise Exception("Other BOUNDS name was given even though name was set after BOUNDS tag.") 438 | if line[0] in ["LO", "UP"]: 439 | bnd[line[1]][line[0]][col_names.index(line[2])] = float(line[3]) 440 | elif line[0] == "FX": 441 | bnd[line[1]]["LO"][col_names.index(line[2])] = float(line[3]) 442 | bnd[line[1]]["UP"][col_names.index(line[2])] = float(line[3]) 443 | elif line[0] == "FR": 444 | bnd[line[1]]["LO"][col_names.index(line[2])] = -math.inf 445 | elif mode == CORE_FILE_BOUNDS_MODE_NO_NAME: 446 | try: 447 | i = bnd_names.index(line[1]) 448 | except: 449 | bnd_names.append(line[1]) 450 | bnd[line[1]] = {"LO": np.zeros(len(col_names)), "UP": np.repeat(math.inf, len(col_names))} 451 | i = -1 452 | if line[0] in ["LO", "UP"]: 453 | bnd[line[1]][line[0]][col_names.index(line[2])] = float(line[3]) 454 | elif line[0] == "FX": 455 | bnd[line[1]]["LO"][col_names.index(line[2])] = float(line[3]) 456 | bnd[line[1]]["UP"][col_names.index(line[2])] = float(line[3]) 457 | elif line[0] == "FR": 458 | bnd[line[1]]["LO"][col_names.index(line[2])] = -math.inf 459 | return name, objective_name, row_names, col_names, col_types, types, c, A, rhs_names, rhs, bnd_names, bnd 460 | 461 | def load_smps(path): 462 | name, objective_name, row_names, col_names, col_types, types, c, A, rhs_names, rhs, bnd_names, bnd = load_mps(path + ".cor") 463 | periods, row_periods, col_periods = _load_time_file(path, name, row_names, col_names) 464 | blocks, independent_variables = _load_stoch_file(path, name, objective_name, row_names, col_names, rhs_names) 465 | return {"name": name, "objective_name": objective_name, "constraints": [(row_names[i], row_periods[i], types[i]) for i in range(len(row_names))], "variables": [(col_names[i], col_periods[i], col_types[i]) for i in range(len(col_names))], "c": c, "A": A, "rhs_names": rhs_names, "rhs": rhs, "bounds": bnd, "periods": periods, "blocks": blocks, "independent_variables": independent_variables} 466 | 467 | 468 | 469 | def load_2stage_problem(path): 470 | d = load_smps(path) 471 | print("Loaded problem", d["name"]) 472 | print("Assuming", d["periods"][0], "to be the deterministic period and", d["periods"][1], "to be the stochastic one.") 473 | assert len(d["periods"]) == 2 474 | deterministic_rows = [i for i in range(len(d["constraints"])) if d["constraints"][i][1] == d["periods"][0]] 475 | stochastic_rows = [i for i in range(len(d["constraints"])) if d["constraints"][i][1] == d["periods"][1]] 476 | deterministic_cols = [i for i in range(len(d["variables"])) if d["variables"][i][1] == d["periods"][0]] 477 | stochastic_cols = [i for i in range(len(d["variables"])) if d["variables"][i][1] == d["periods"][1]] 478 | 479 | A = d["A"][deterministic_rows,:][:,deterministic_cols] 480 | assert np.count_nonzero(d["A"][deterministic_rows,:][:,stochastic_cols]) == 0 481 | W = d["A"][stochastic_rows,:][:,stochastic_cols] 482 | T_matrix = d["A"][stochastic_rows,:][:,deterministic_cols] 483 | 484 | c = d["c"][deterministic_cols] 485 | q_array = d["c"][stochastic_cols] 486 | 487 | assert len(d["rhs_names"]) == 1 488 | b = d["rhs"][d["rhs_names"][0]][deterministic_rows] 489 | h_array = d["rhs"][d["rhs_names"][0]][stochastic_rows] 490 | 491 | for key, bound in d["bounds"].items(): 492 | assert np.count_nonzero(bound["LO"]) == 0 493 | assert np.sum(np.isinf(bound["UP"])) == bound["UP"].size 494 | 495 | # Assert A and W are not stochastic 496 | for key, block in d["blocks"].items(): 497 | if isinstance(block, Block): 498 | for stochastic in block.cases: 499 | for entries in stochastic["A"]: 500 | assert not (entries[0] in stochastic_rows and entries[1] in stochastic_cols) 501 | assert not (entries[0] in deterministic_rows and entries[1] in deterministic_cols) 502 | assert not (entries[0] in deterministic_rows and entries[1] in stochastic_cols) 503 | for indep in d["independent_variables"]: 504 | assert not (indep[0] in stochastic_rows and indep[1] in stochastic_cols) 505 | assert not (indep[0] in deterministic_rows and indep[1] in deterministic_cols) 506 | assert not (indep[0] in deterministic_rows and indep[1] in stochastic_cols) 507 | 508 | # Assert stochastic elements appear only in phase 2 509 | for key, block in d["blocks"].items(): 510 | assert block.period == d["periods"][1] 511 | for key, indep in d["independent_variables"].items(): 512 | assert indep["period"] == d["periods"][1] 513 | 514 | determ_cnstr = [row[2] for row in d["constraints"] if row[1] == d["periods"][0]] 515 | stochastic_cnstr = [row[2] for row in d["constraints"] if row[1] == d["periods"][1]] 516 | #deterministic 517 | determ_ineq = sum(x != "E" for x in determ_cnstr) 518 | c = np.concatenate((c, np.zeros(determ_ineq))) 519 | T_matrix = np.concatenate((T_matrix, np.zeros((T_matrix.shape[0], determ_ineq))), axis = 1) 520 | A_add = np.zeros((A.shape[0], determ_ineq)) 521 | k = 0 522 | for i in range(A.shape[0]): 523 | if determ_cnstr[i] != "E": 524 | A_add[i, k] = 1 if determ_cnstr[i] == "L" else -1 525 | k = k + 1 526 | A = np.concatenate((A, A_add), axis = 1) 527 | 528 | stochastic_ineq = sum(x != "E" for x in stochastic_cnstr) 529 | q_array = np.concatenate((q_array, np.zeros(stochastic_ineq))) 530 | W_add = np.zeros((W.shape[0], stochastic_ineq)) 531 | k = 0 532 | for i in range(W.shape[0]): 533 | if stochastic_cnstr[i] != "E": 534 | W_add[i, k] = 1 if stochastic_cnstr[i] == "L" else -1 535 | k = k + 1 536 | W = np.concatenate((W, W_add), axis = 1) 537 | 538 | T = [T_matrix] 539 | h = [h_array] 540 | q = [q_array] 541 | p = [1] 542 | 543 | for key, indep in d["independent_variables"].items(): 544 | T_next = [] 545 | h_next = [] 546 | q_next = [] 547 | p_next = [] 548 | for vp in indep["distrib"]: 549 | for i in range(len(T)): 550 | if key[0] < 0: 551 | j = stochastic_cols.index(key[1]) 552 | # objective 553 | add = np.copy(q[i]) 554 | add[j] = vp[0] 555 | q_next.append(add) 556 | p_next.append(p[i] * vp[1]) 557 | T_next.append(T[i]) 558 | h_next.append(h[i]) 559 | elif key[1] < 0: 560 | k = stochastic_rows.index(key[0]) 561 | add = np.copy(h[i]) 562 | add[k] = vp[0] 563 | h_next.append(add) 564 | p_next.append(p[i] * vp[1]) 565 | T_next.append(T[i]) 566 | q_next.append(q[i]) 567 | else: 568 | k = stochastic_rows.index(key[0]) 569 | j = deterministic_cols.index(key[1]) 570 | add = np.copy(T[i]) 571 | add[k, j] = vp[0] 572 | T_next.append(add) 573 | p_next.append(p[i] * vp[1]) 574 | q_next.append(q[i]) 575 | h_next.append(h[i]) 576 | T = T_next 577 | h = h_next 578 | q = q_next 579 | p = p_next 580 | 581 | for key, block in d["blocks"].items(): 582 | T_next = [] 583 | h_next = [] 584 | q_next = [] 585 | p_next = [] 586 | if isinstance(block, Block): 587 | for i in range(len(block.probabilities)): 588 | for t in range(len(T)): 589 | # handle T 590 | T_add = np.copy(T[t]) 591 | h_add = np.copy(h[t]) 592 | q_add = np.copy(q[t]) 593 | for location, value in block.cases[i]["A"].items(): 594 | k = stochastic_rows.index(location[0]) 595 | j = deterministic_cols.index(location[1]) 596 | T_add[k, j] = value 597 | for location, value in block.cases[i]["b"].items(): 598 | k = stochastic_rows.index(location) 599 | h_add[k] = value 600 | for location, value in block.cases[i]["c"].items(): 601 | j = deterministic_cols.index(location) 602 | q_add[j] = value 603 | T_next.append(T_add) 604 | h_next.append(h_add) 605 | q_next.append(q_add) 606 | p_next.append(p[t] * block.probabilities[i]) 607 | T = T_next 608 | h = h_next 609 | q = q_next 610 | p = p_next 611 | 612 | assert len(T) == len(h) 613 | assert len(T) == len(p) 614 | assert len(T) == len(q) 615 | 616 | return {"name": d["name"], "c": c, "A": A, "b": b, "q": q, "h": h, "T": T, "W": W, "p": p} 617 | 618 | -------------------------------------------------------------------------------- /old/dist/pysmps-1.5.6-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmaerte/pysmps/aacdfc26959a0b267b331860e1fb717d924b3a52/old/dist/pysmps-1.5.6-py3-none-any.whl -------------------------------------------------------------------------------- /old/dist/pysmps-1.5.6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmaerte/pysmps/aacdfc26959a0b267b331860e1fb717d924b3a52/old/dist/pysmps-1.5.6.tar.gz -------------------------------------------------------------------------------- /old/pysmps.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: pysmps 3 | Version: 1.5.6 4 | Summary: Utilities for parsing MPS and SMPS file formats. 5 | Home-page: https://github.com/jmaerte/pysmps 6 | Author: Julian Maerte 7 | Author-email: maertej@students.uni-marburg.de 8 | License: UNKNOWN 9 | Description: # pysmps 10 | 11 | This is a utility script for parsing MPS and SMPS file formats. It offers two main functions `load_mps` for loading mps files and `load_smps` for loading smps file directory. 12 | 13 | ### `load_mps` 14 | 15 | The `load_mps(path)` method takes a `path` variable as input. It should be a .cor or .mps file. 16 | It opens the file with read-permissions and parses the described linear program into the following format: 17 | 18 | - `name`: The name given to the linear program (can't be blank) 19 | - `objective_name`: The name of the objective function value 20 | - `row_names`: list of row names 21 | - `col_names`: list of column names 22 | - `types`: list of constraint type indicators, i.e. either "E", "L" or "G" for equality, lower/equal or greater/equal constraint respectively. 23 | - `c`: the objective function coefficients 24 | - `A`: the constraint matrix 25 | - `rhs_names`: list of names of right hand sides (there can be multiple right hand side components be defined, seldom more than one though) 26 | - `rhs`: dictionary `(rhs_name) => b`, where `b` is the vector of constraint values for that given right hand side name. 27 | - `bnd_names`: list of names of box-bounds (seldom more than one) 28 | - `bnd`: dictionary `(bnd_name) => {"LO": v_l, "UP": v_u}` where `v_l` is the vector of lower bounds and `v_u` is the vector of upper bound values (defaults to `v_l = 0` and `v_u = +inf`). 29 | 30 | Finally this corresponds to the linear program 31 | 32 | ```python 33 | min c * x 34 | 35 | s.t. for each rhs_name with corresponding b: 36 | 37 | A[types == "E",:] * x = b[types == "E"] 38 | A[types == "L",:] * x <= b[types == "L"] 39 | A[types == "G",:] * x >= b[types == "G"] 40 | 41 | for each bnd_name with corresponding v_l and v_u: 42 | 43 | v_l <= x < v_u 44 | 45 | ``` 46 | 47 | ### `load_smps` 48 | 49 | This function makes use of the `load_mps` function for parsing the .cor file. The SMPS file format consists of three files, a .cor, .tim and .sto file. The .cor file is in MPS format. Further the function expects a parameter `path` to be such that `path + ".cor"` is the core file, `path + ".tim"` the time file and `path + ".sto"` is the stochastic file. 50 | It *does not* support scenarios yet! 51 | It returns a stochastic multi-stage problem in the following format 52 | 53 | - `name`: name of the program (must be the same in all 3 files) 54 | 55 | - `objective_name`: name of the objective function value 56 | 57 | - `constraints`: list of tuples `(name, period, type)` for each constraint. It gives a name, a period in which the constraints appears and a type, i.e. "E", "L" or "G" as in MPS. 58 | 59 | - `variables`: list of tuples `(name, period, type)` for each variable. It defines a name and a period in which the variable joins the program. `type` denotes a string that is either integral or continuous depending on whether the INTORG MARKER was set when instantiating the variable. 60 | 61 | - `c`: vector of objective function coefficients (of all periods) 62 | 63 | - `A`: matrix of constraint coefficients (of all periods) 64 | 65 | - `rhs_names`: list of rhs names as in MPS 66 | 67 | - `rhs`: dictionary as in MPS 68 | 69 | - `bounds`: dictionary as in MPS 70 | 71 | - `periods`: list of all periods appearing. `len(periods)` is the number stages. 72 | 73 | - `blocks`: dictionary of `Block`,`LinearTransform` or `SubRoutine` objects. Dependent on what the .sto file defined. `Blocks` are independent random variables (every case of a `Block` must be combined with each case of another `Block` to get all possible appearences; the probabilities multiply), `LinearTransform` are linear transformations of continuous random variables. The user needs to write the sample script on his own. `SubRoutine` is a left-out in the file; it presupposes the user to know what to do with these values. 74 | 75 | - `independent_variables`: dictionary `((i,j)) => {position, period, distrib}`, where `(i,j)` is the tuple of row/column indices. If one of them is `-1` this means that it's either an objective value or a rhs-value respectively. `position` is a dictionary adapting to where the entry is (objective value, rhs value or matrix value), `period` defines the period in which this variable is stochastic, `distrib` is either a definition of a continuous random variables 76 | 77 | ```python 78 | distrib: {type: "N(mu, sigma**2)"/"U(a, b)"/"B(p, q)"/"G(p, b)"/"LN(mu, sigma**2)", parameters} 79 | ``` 80 | 81 | where parameters is a dictionary defining the required parameters. In the discrete case it is a list of tuples `(v,p)`, where `v` is the value of this position and `p` is the probability of it appearing. 82 | 83 | For an example on how to use this format i recommand looking at the code for `load_2stage_problem`. 84 | 85 | ### `load_2stage_problem` 86 | 87 | Loads a SMPS directory and tries to bring it into a 2-staged stochastic linear program with fixed recourse. Output is a dictionary containing the values 88 | 89 | - `c`: first stage objective function value 90 | - `A`: first stage (equality) constraint coefficient matrix 91 | - `b`: first stage constraint values 92 | - `q`: list of second stage objective function coefficients (each case one entry) 93 | - `h`: list of second stage constraint values (each case one entry) 94 | - `T`: list of second stage constraint values for deterministic variables (each case one entry) 95 | - `W`: recourse matrix (since it's fixed recourse this is not a list) 96 | - `p`: list of probabilities for each case 97 | 98 | The constellations in which `(q,h,T,W)` appear are the realizations given by `(q[k], h[k], T[k], W)`. 99 | The problem then resembles one of the form 100 | 101 | ```python 102 | min c * x + E_p[q * y] 103 | 104 | s.t. A * x = b 105 | T * x + W * y = h 106 | x, y >= 0 107 | ``` 108 | 109 | which is a formal expression since T and h are also stochastic. In fact this notation means we assert the stochastic constraints inside of the expectation, making it a function of x only. 110 | 111 | For casting the SMPS files into such a form we need to make certain assertments: 112 | 113 | - The upper right matrix needs to be zeroes only. 114 | - We only have one righthand side defined (`len(rhs_names) == 1`). 115 | - There are no boundaries or if we defined some they are the default values. 116 | - The first period parsed from the time file is the deterministic one, the other one is the stochastic one (especially there can only be two periods). 117 | - `A` and `W` are not stochastic. 118 | 119 | This script however does 120 | 121 | - convert inequality constraints (deterministic and stochastic) into equality constraints by adding slack variables at the right places 122 | - calculate all combinations of independent accurances of stochastic components (BLOCKS and INDEP) 123 | - calculate the probabilities as products of independent elementary probabilities alongside. 124 | 125 | 126 | Platform: UNKNOWN 127 | Description-Content-Type: text/markdown 128 | -------------------------------------------------------------------------------- /old/pysmps.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | .gitignore 2 | LICENSE 3 | PYPI_README.md 4 | README.md 5 | requirements.txt 6 | requirements_dev.txt 7 | setup.py 8 | pysmps/__init__.py 9 | pysmps/smps_loader.py 10 | pysmps.egg-info/PKG-INFO 11 | pysmps.egg-info/SOURCES.txt 12 | pysmps.egg-info/dependency_links.txt 13 | pysmps.egg-info/top_level.txt -------------------------------------------------------------------------------- /old/pysmps.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /old/pysmps.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | pysmps 2 | -------------------------------------------------------------------------------- /pysmps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmaerte/pysmps/aacdfc26959a0b267b331860e1fb717d924b3a52/pysmps/__init__.py -------------------------------------------------------------------------------- /pysmps/__pycache__/mps_loader.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmaerte/pysmps/aacdfc26959a0b267b331860e1fb717d924b3a52/pysmps/__pycache__/mps_loader.cpython-38.pyc -------------------------------------------------------------------------------- /pysmps/mps_loader.py: -------------------------------------------------------------------------------- 1 | import re 2 | import math 3 | import copy 4 | import warnings 5 | 6 | CORE_FILE_ROW_MODE = "ROWS" 7 | CORE_FILE_COL_MODE = "COLUMNS" 8 | CORE_FILE_RHS_MODE = "RHS" 9 | CORE_FILE_BOUNDS_MODE = "BOUNDS" 10 | CORE_FILE_RANGES_MODE = "RANGES" 11 | CORE_FILE_SOS_MODE = "SOS" 12 | 13 | # SMPS special tags 14 | CORE_FILE_ARCS_MODE = "ARCS" 15 | 16 | CORE_FILE_BOUNDS_MODE_NAME_GIVEN = "BOUNDS_NAME" 17 | CORE_FILE_BOUNDS_MODE_NO_NAME = "BOUNDS_NO_NAME" 18 | CORE_FILE_RHS_MODE_NAME_GIVEN = "RHS_NAME" 19 | CORE_FILE_RHS_MODE_NO_NAME = "RHS_NO_NAME" 20 | CORE_FILE_RANGES_MODE_NAME_GIVEN = "RANGES_NAME" 21 | CORE_FILE_RANGES_MODE_NO_NAME = "RANGES_NO_NAME" 22 | CORE_FILE_SOS_MODE_FIRST_LINE = "SOS_FIRST_LINE" 23 | 24 | 25 | ROW_MODE_OBJ = "N" 26 | 27 | MODE = {CORE_FILE_ROW_MODE: 0, CORE_FILE_COL_MODE: 1, CORE_FILE_RHS_MODE: 3, 28 | CORE_FILE_BOUNDS_MODE: 4, CORE_FILE_BOUNDS_MODE_NAME_GIVEN: 5, 29 | CORE_FILE_BOUNDS_MODE_NO_NAME: 6, CORE_FILE_RHS_MODE_NAME_GIVEN: 7, 30 | CORE_FILE_RHS_MODE_NO_NAME: 8, CORE_FILE_RANGES_MODE_NAME_GIVEN: 9, 31 | CORE_FILE_RANGES_MODE_NO_NAME: 10, CORE_FILE_SOS_MODE: 11, 32 | CORE_FILE_SOS_MODE_FIRST_LINE: 12} 33 | 34 | COL_TYPES = {1: "Integer", 0: "Continuous"} 35 | BND_TYPES = ["LO", "UP"] 36 | BND_TYPE = {"LO": "lower", "UP": "upper"} 37 | BND_FUNC = {"LO": lambda a, b : a if b == 0 else max(a,b), "UP": min} 38 | 39 | 40 | 41 | 42 | 43 | TIME_FILE_PERIODS_MODE = "PERIODS" 44 | TIME_FILE_PERIODS_MODE_EXPLICIT = "PERIODS_EXPLICIT" 45 | TIME_FILE_PERIODS_MODE_IMPLICIT = "PERIODS_IMPLICIT" 46 | TIME_FILE_ROWS_MODE = "ROWS" 47 | TIME_FILE_COLS_MODE = "COLUMNS" 48 | 49 | 50 | STOCH_FILE_SIMPLE_MODE = "SIMPLE" 51 | STOCH_FILE_CHANCE_MODE = "CHANCE" 52 | STOCH_FILE_SCENARIOS_MODE = "SCENARIOS" 53 | STOCH_FILE_NODES_MODE = "NODES" 54 | STOCH_FILE_INDEP_MODE = "INDEP" 55 | STOCH_FILE_BLOCKS_MODE = "BLOCKS" 56 | STOCH_FILE_DISTRIB_MODE = "DISTRIB" 57 | STOCH_FILE_BLOCKS_BL_MODE = "BL" 58 | 59 | # BLOCKS modes 60 | STOCH_FILE_BLOCKS_DISCRETE_MODE = "BLOCKS_DISCRETE" 61 | STOCH_FILE_BLOCKS_SUB_MODE = "BLOCKS_SUB" 62 | STOCH_FILE_BLOCKS_LINTR_MODE = "BLOCKS_LINTR" 63 | 64 | #INDEP modes 65 | STOCH_FILE_INDEP_DISCRETE_MODE = "INDEP_DISCRETE" 66 | STOCH_FILE_INDEP_DISTRIB_MODE = "INDEP_DISTRIB" 67 | 68 | 69 | warnings.simplefilter("always", ImportWarning) 70 | warnings.warn("""pysmps: Backwards compatibility to pre-2.0 versions has been abolished. Install older versions for code adjusted to the old interface, e.g. via \r\n 71 | pip install 'pysmps<2.0'\r\n""", category=ImportWarning, stacklevel=2) 72 | warnings.simplefilter("default", ImportWarning) 73 | 74 | # public 75 | class MPS: 76 | 77 | def __init__(self, defaults): 78 | self.defaults = {"Integer": {"type": "Integer", "lower": defaults["i_lower"], "upper": defaults["i_upper"]}, "Continuous": {"type": "Continuous", "lower": defaults["c_lower"], "upper": defaults["c_upper"]}} 79 | self.name = "No Name" 80 | self.objectives = {} 81 | self.variables = {} 82 | self._variables = {} 83 | self.constraints = {} 84 | self.rhs = {} 85 | self.offsets = {} 86 | self.ranges = {} 87 | #self.sos = {} 88 | 89 | self.curr_rhs = -1 90 | self.curr_bnd = -1 91 | self.curr_range = -1 92 | 93 | 94 | def finalize(self): 95 | if len(self.bnd_names()) > 0: self.curr_bnd = self.bnd_names()[0] 96 | if len(self.rhs_names()) > 0: self.curr_rhs = self.rhs_names()[0] 97 | if len(self.range_names()) > 0: self.curr_range = self.range_names()[0] 98 | #if len(self.sos_names()) > 0: self.curr_sos = self.sos_names()[0] 99 | 100 | def _set_name(self, name): 101 | self.name = name 102 | 103 | def add_objective(self, name): 104 | self.objectives[name] = {"coefficients": {}} 105 | 106 | 107 | # returns the list of names of possible boundary configurations 108 | def objective_names(self): 109 | return list(self.objectives.keys()) 110 | def bnd_names(self): 111 | return list(self.variables.keys()) 112 | def rhs_names(self): 113 | return list(self.rhs.keys()) 114 | def variable_names(self): 115 | return list(self._variables.keys()) 116 | def constraint_names(self): 117 | return list(self.constraints.keys()) 118 | def range_names(self): 119 | return list(self.ranges.keys()) 120 | #def sos_names(self): 121 | # return list(self.sos.keys()) 122 | 123 | 124 | 125 | def get_variables(self): 126 | return self.variables[self.curr_bnd] 127 | def get_rhs(self): 128 | return self.rhs[self.curr_rhs] 129 | def get_constraints(self): 130 | return self.constraints 131 | def get_objectives(self): 132 | return self.objectives 133 | def get_offsets(self): 134 | return self.offsets[self.curr_rhs] 135 | def get_ranges(self): 136 | return self.ranges[self.curr_range] 137 | 138 | 139 | def _add_constraint(self, name, constr): 140 | self.constraints[name] = constr 141 | 142 | def _set_coefficient(self, row, variable, value): 143 | if row in self.objectives: 144 | self.objectives[row]["coefficients"][variable] = value 145 | else: 146 | self.constraints[row]["coefficients"][variable] = value 147 | 148 | 149 | #def add_sos(self, name, degree): 150 | # if name in self.sos: 151 | # raise ValueError('The SOS ' + name + ' does already exist!') 152 | # self.sos[name] = {"degree": degree, "priority": priority, "variables": {}} 153 | 154 | #def attach_sos(self, name): 155 | # self.curr_sos = name 156 | 157 | #def add_variable_to_sos(self, var, value): 158 | # self.sos[self.curr_sos]["variables"][var] = value 159 | 160 | # sets the boundary group active for modification 161 | def attach_bnd(self, name): 162 | if name not in self.variables: 163 | raise ValueError('The boundary group ' + name + ' does not exist!') 164 | self.curr_bnd = name 165 | 166 | def _add_variable(self, name, _type): 167 | self._variables[name] = {"type": _type, "lower": self.defaults[_type]["lower"], "upper": self.defaults[_type]["upper"]} 168 | def _update_variable(self, variable, mod): 169 | self.variables[self.curr_bnd][variable].update(mod) 170 | 171 | def _add_bnd_group(self, name): 172 | if name in self.variables: 173 | raise ValueError('The boundary group ' + name + ' already exists!') 174 | self.variables[name] = copy.deepcopy(self._variables) 175 | if len(self.variables) == 2: 176 | warnings.warn("There are multiple boundary groups for the linear program " + self.name + ". Switch boundary group using the MPS.set_bounds_group function.", stacklevel=2) 177 | 178 | def attach_range(self, name): 179 | if name not in self.ranges: 180 | raise ValueError('The range group ' + name + ' does not exist!') 181 | self.curr_range = name 182 | 183 | def _add_range_group(self, name): 184 | if name in self.ranges: 185 | raise ValueError('The range group ' + name + ' already exists!') 186 | self.ranges[name] = {} 187 | if len(self.ranges) == 2: 188 | warnings.warn("There are multiple range groups for the linear program " + self.name + ". Switch range group using the MPS.attach_range function.", stacklevel=2) 189 | 190 | def add_range(self, constr, value): 191 | if self.constraints[constr]["type"] == "G": 192 | self.ranges[self.curr_range][constr] = {"lower": 0, "upper": abs(value)} 193 | elif self.constraints[constr]["type"] == "L": 194 | self.ranges[self.curr_range][constr] = {"lower": -abs(value), "upper": 0} 195 | elif self.constraint[constr]["type"] == "E": 196 | if constr not in self.ranges[self.curr_range]: 197 | self.ranges[self.curr_range] = {"lower": 0, "upper": 0} 198 | if value < 0: 199 | self.ranges[self.curr_range][constr]["lower"] = value 200 | else: 201 | self.ranges[self.curr_range][constr]["upper"] = value 202 | else: 203 | raise ValueError('Range given for free row!') 204 | 205 | def _add_rhs_group(self, name): 206 | if name in self.rhs: 207 | raise ValueError('The RHS group ' + name + ' already exists!') 208 | self.rhs[name] = dict((constr,0) for constr in self.constraints) 209 | self.offsets[name] = dict((obj,0) for obj in self.objectives) 210 | if len(self.rhs) == 2: 211 | warnings.warn("There are multiple rhs groups for the linear program " + self.name + ". Switch rhs group using the MPS.attach_rhs function.", stacklevel=2) 212 | 213 | def attach_rhs(self, name): 214 | if name not in self.rhs: 215 | raise ValueError('The RHS group ' + name + ' does not exist!') 216 | self.curr_rhs = name 217 | 218 | def set_rhs(self, constr, value): 219 | if constr in self.constraints: 220 | self.rhs[self.curr_rhs][constr] = value 221 | elif constr in self.objectives: 222 | self.offsets[self.curr_rhs][constr] = value 223 | else: 224 | raise ValueError('The row ' + constr + ' does not exist!') 225 | 226 | def get_curr_rhs(self): 227 | return self.curr_rhs 228 | def get_curr_bnd(self): 229 | return self.curr_bnd 230 | def get_curr_range(self): 231 | return self.curr_range 232 | 233 | # used to produce dict 234 | def __iter__(self): 235 | yield ("name", self.name) 236 | yield ("objective_names", self.objective_names()) 237 | yield ("bnd_names", self.bnd_names()) 238 | yield ("rhs_names", self.rhs_names()) 239 | yield ("range_names", self.range_names()) 240 | yield ("constraint_names", self.constraint_names()) 241 | yield ("variable_names", list(self._variables.keys())) 242 | #yield ("sos_names", self.sos_names()) 243 | yield ("objectives", self.objectives) 244 | yield ("variables", self.variables) 245 | yield ("constraints", self.constraints) 246 | yield ("rhs", self.rhs) 247 | yield ("offsets", self.offsets) 248 | yield ("ranges", self.ranges) 249 | #yield ("sos", self.sos) 250 | 251 | 252 | def read_mps(path, **kwargs): 253 | mode = -1 254 | 255 | default_bounds = {"c_lower": 0.0, "c_upper": math.inf, "i_lower": 0.0, "i_upper": math.inf} 256 | given_bounds = dict((k, kwargs[k]) for k in ['c_lower', 'c_upper', 'i_lower', 'i_upper'] if k in kwargs) 257 | default_bounds.update(given_bounds) 258 | 259 | mps = MPS(default_bounds) 260 | 261 | defaults = {"MI_upper": 0.0, "SC_lower": 1.0} 262 | given_defaults = dict((k, kwargs[k]) for k in ['MI_upper', 'SC_lower'] if k in kwargs) 263 | defaults.update(given_defaults) 264 | 265 | integral_marker = False 266 | 267 | with open(path, "r") as reader: 268 | 269 | for line in reader: 270 | if line.startswith("*"): 271 | continue 272 | 273 | line = re.split(" |\t", line) 274 | line = [x.strip() for x in line] 275 | line = list(filter(None, line)) 276 | 277 | if len(line) == 0: 278 | continue 279 | if line[0] == "ENDATA": 280 | mps.finalize() 281 | break 282 | if line[0] == "*": 283 | continue 284 | if line[0] == "NAME": 285 | mps._set_name(line[1]) 286 | #name = line[1] 287 | 288 | # ROW INSTRUCTION 289 | elif line[0] in [CORE_FILE_ROW_MODE, CORE_FILE_COL_MODE]: 290 | mode = MODE[line[0]] 291 | 292 | # RHS INSTRUCTION 293 | elif line[0] == CORE_FILE_RHS_MODE and len(line) <= 2: 294 | if len(line) > 1: 295 | if line[1] not in mps.rhs: 296 | mps._add_rhs_group(line[1]) 297 | mps.attach_rhs(line[1]) 298 | 299 | mode = MODE[CORE_FILE_RHS_MODE_NAME_GIVEN] 300 | else: 301 | mode = MODE[CORE_FILE_RHS_MODE_NO_NAME] 302 | 303 | # BOUNDS INSTRUCTION 304 | elif line[0] == CORE_FILE_BOUNDS_MODE and len(line) <= 2: 305 | if len(line) > 1: 306 | if line[1] not in mps.variables: 307 | mps._add_bnd_group(line[1]) 308 | mps.attach_bnd(line[1]) 309 | 310 | mode = MODE[CORE_FILE_BOUNDS_MODE_NAME_GIVEN] 311 | else: 312 | mode = MODE[CORE_FILE_BOUNDS_MODE_NO_NAME] 313 | 314 | # RANGES INSTRUCTION 315 | elif line[0] == CORE_FILE_RANGES_MODE and len(line) <= 2: 316 | if len(line) > 1: 317 | if line[1] not in mps.ranges: 318 | mps._add_range_group(line[1]) 319 | mps.attach_range(line[1]) 320 | 321 | mode = MODE[CORE_FILE_RANGES_MODE_NAME_GIVEN] 322 | else: 323 | mode = MODE[CORE_FILE_RANGES_MODE_NO_NAME] 324 | 325 | # SOS INSTRUCTION 326 | elif line[0] == CORE_FILE_SOS_MODE: 327 | mode = MODE[CORE_FILE_SOS_MODE_FIRST_LINE] 328 | 329 | 330 | elif line[0] == CORE_FILE_ARCS_MODE: 331 | raise ValueError("NETWORK is currently not implemented!") 332 | 333 | 334 | elif mode == MODE[CORE_FILE_ROW_MODE]: 335 | if line[0] == ROW_MODE_OBJ: 336 | mps.add_objective(line[1]) 337 | else: 338 | mps._add_constraint(line[1], {"type": line[0], "coefficients": {}}) 339 | 340 | # COL MODE 341 | elif mode == MODE[CORE_FILE_COL_MODE]: 342 | if len(line) > 1 and line[1] == "'MARKER'": 343 | if line[2] == "'INTORG'": 344 | integral_marker = True 345 | elif line[2] == "'INTEND'": 346 | integral_marker = False 347 | continue 348 | if line[0] not in mps._variables: 349 | mps._add_variable(line[0], COL_TYPES[integral_marker]) 350 | j = 1 351 | while j < len(line) - 1: 352 | mps._set_coefficient(line[j], line[0], float(line[j+1])) 353 | j = j + 2 354 | 355 | # RHS MODE 356 | elif mode in [MODE[CORE_FILE_RHS_MODE_NAME_GIVEN], MODE[CORE_FILE_RHS_MODE_NO_NAME]]: 357 | if mode == MODE[CORE_FILE_RHS_MODE_NAME_GIVEN]: 358 | if line[0] != mps.get_curr_rhs(): 359 | raise Exception("Other RHS name was given even though name was set after RHS tag.") 360 | elif line[0] not in mps.rhs: 361 | mps._add_rhs_group(line[0]) 362 | mps.attach_rhs(line[0]) 363 | for kk in range((len(line) - 1) // 2): 364 | idx = kk * 2 365 | mps.set_rhs(line[idx + 1], float(line[idx + 2])) 366 | 367 | # SOS MODE 368 | elif mode == MODE[CORE_FILE_SOS_MODE_FIRST_LINE]: 369 | warnings.warn("SOS functionality is currently not supported. If your program relies on Special Ordered Set functionality the output might not be true to the file input!", stacklevel=2) 370 | # degree = int(line[0][1:]) 371 | # mps.add_sos(line[2], degree, int(line[3])) 372 | # mps.attach_sos(line[2]) 373 | mode = MODE[CORE_FILE_SOS_MODE] 374 | 375 | 376 | elif mode == MODE[CORE_FILE_SOS_MODE]: 377 | pass 378 | #mps.add_variable_to_sos(line[1], float(line[2])) 379 | 380 | # BOUNDS MODE 381 | elif mode in [MODE[CORE_FILE_BOUNDS_MODE_NAME_GIVEN], MODE[CORE_FILE_BOUNDS_MODE_NO_NAME]]: 382 | if mode == MODE[CORE_FILE_BOUNDS_MODE_NAME_GIVEN]: 383 | if line[1] != mps.get_curr_bnd(): 384 | raise Exception("Other BND name was given even though name was set after BND tag.") 385 | elif line[1] not in mps.variables: 386 | mps._add_bnd_group(line[1]) 387 | mps.attach_bnd(line[1]) 388 | 389 | if line[0] == "UP": 390 | mps._update_variable(line[2], {"upper": float(line[3])}) 391 | elif line[0] == "UI": 392 | mps._update_variable(line[2], {"type": "Integer", "upper": float(line[3])}) 393 | elif line[0] == "LO": 394 | mps._update_variable(line[2], {"lower": float(line[3])}) 395 | elif line[0] == "LI": 396 | mps._update_variable(line[2], {"type": "Integer", "lower": float(line[3])}) 397 | elif line[0] == "FX": 398 | mps._update_variable(line[2], {"lower": float(line[3]), "upper": float(line[3])}) 399 | elif line[0] == "FR": 400 | mps._update_variable(line[2], {"lower": -math.inf, "upper": math.inf}) 401 | elif line[0] == "MI": 402 | mps._update_variable(line[2], {"lower": -math.inf, "upper": defaults["MI_upper"]}) 403 | elif line[0] == "PL": 404 | mps._update_variable(line[2], {"lower": 0.0, "upper": math.inf}) 405 | elif line[0] == "BV": 406 | mps._update_variable(line[2], {"type": "Integer", "lower": 0.0, "upper": 1.0}) 407 | elif line[0] == "SC": 408 | val = defaults["SC_lower"] 409 | if len(line) > 3: 410 | val = float(line[3]) 411 | mps._update_variable(line[2], {"type": "Semi-Continuous", "lower": val}) 412 | 413 | # RANGES MODE 414 | elif mode in [MODE[CORE_FILE_RANGES_MODE_NAME_GIVEN], MODE[CORE_FILE_RANGES_MODE_NO_NAME]]: 415 | if mode == MODE[CORE_FILE_RANGES_MODE_NAME_GIVEN]: 416 | if line[0] != mps.get_curr_range(): 417 | raise Exception("Other RANGES group tag was given even though group was set after RANGES tag.") 418 | elif line[0] not in mps.ranges: 419 | mps._add_range_group(line[0]) 420 | mps.attach_range(line[0]) 421 | 422 | mps.add_range(line[1], float(line[2])) 423 | 424 | 425 | return mps 426 | 427 | 428 | 429 | 430 | 431 | 432 | -------------------------------------------------------------------------------- /pysmps/smps_loader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun Sep 8 13:28:53 2019 4 | 5 | @author: Julian Märte 6 | """ 7 | 8 | import re 9 | import math 10 | import copy 11 | import warnings 12 | from mps_loader import MPS, read_mps 13 | 14 | TIME_FILE_PERIODS_MODE = "PERIODS" 15 | TIME_FILE_PERIODS_MODE_EXPLICIT = "PERIODS_EXPLICIT" 16 | TIME_FILE_PERIODS_MODE_IMPLICIT = "PERIODS_IMPLICIT" 17 | TIME_FILE_ROWS_MODE = "ROWS" 18 | TIME_FILE_COLS_MODE = "COLUMNS" 19 | 20 | TIME_MODE = {TIME_FILE_PERIODS_MODE: 0, TIME_FILE_PERIODS_MODE_EXPLICIT: 1, 21 | TIME_FILE_PERIODS_MODE_IMPLICIT: 2, TIME_FILE_ROWS_MODE: 3, 22 | TIME_FILE_COLS_MODE: 4} 23 | 24 | 25 | STOCH_FILE_INDEP_MODE = "INDEP" 26 | STOCH_FILE_INDEP_DISCRETE_MODE = "INDEP_DISCRETE" 27 | STOCH_FILE_INDEP_DISTRIB_MODE = "INDEP_DISTRIB" 28 | STOCH_FILE_INDEP_SUB_MODE = "SUB" 29 | STOCH_FILE_BLOCKS_MODE = "BLOCKS" 30 | STOCH_FILE_BLOCKS_DISCRETE_MODE = "BLOCKS_DISCRETE" 31 | STOCH_FILE_BLOCKS_SUB_MODE = "BLOCKS_SUB" 32 | STOCH_FILE_BLOCKS_LINTR_MODE = "BLOCKS_LINTR" 33 | STOCH_FILE_BLOCKS_BL_MODE = "BL" 34 | STOCH_FILE_SCENARIOS_MODE = "SCENARIOS" 35 | 36 | 37 | STOCH_MODE = {STOCH_FILE_INDEP_MODE: 0, STOCH_FILE_INDEP_DISCRETE_MODE: 1, 38 | STOCH_FILE_INDEP_DISTRIB_MODE: 2, STOCH_FILE_INDEP_SUB_MODE: 3, 39 | STOCH_FILE_BLOCKS_MODE: 4, STOCH_FILE_BLOCKS_BL_MODE: 5, 40 | STOCH_FILE_BLOCKS_DISCRETE_MODE: 6, STOCH_FILE_BLOCKS_SUB_MODE: 7, 41 | STOCH_FILE_BLOCKS_LINTR_MODE: 8, STOCH_FILE_BLOCKS_BL_MODE: 9} 42 | 43 | 44 | 45 | class SMPS: 46 | 47 | def __init__(self, mps): 48 | self.mps = mps 49 | 50 | 51 | self.rows = self.mps.constraint_names() 52 | self.cols = self.mps.variable_names() 53 | 54 | self.row_periods = {} 55 | self.col_periods = {} 56 | 57 | self.distributions = {} 58 | self.blocks = {} 59 | 60 | self.curr_block = None 61 | self.curr_block_period = None 62 | 63 | self.curr_lintr_period = None 64 | self.curr_lintr_block = None 65 | self.curr_lintr = None 66 | self.curr_lintr_name = None 67 | 68 | 69 | 70 | 71 | # TIME RELATED FUNCTIONS 72 | def start_implicit(self): 73 | self.last_row_period = None 74 | self.last_row = -1 75 | self.last_col_period = None 76 | self.last_col = -1 77 | self.implicit = True 78 | 79 | def finalize_implicit(self): 80 | if self.implicit: 81 | self._add_col_period_implicit(None, len(self.cols)) 82 | self._add_row_period_implicit(None, len(self.rows)) 83 | 84 | def get_periods(self): 85 | return list(self.periods.keys()) 86 | 87 | def _add_row_period_implicit(self, period, r): 88 | if self.last_row_period: 89 | if self.last_row_period in self.row_periods: 90 | self.row_periods[self.last_row_period].extend(self.rows[(self.last_row):r]) 91 | else: 92 | self.row_periods[self.last_row_period] = copy.deepcopy(self.rows[(self.last_row):r]) 93 | self.last_row_period = period 94 | self.last_row = r 95 | 96 | def _add_col_period_implicit(self, period, c): 97 | if self.last_col_period: 98 | if self.last_col_period in self.col_periods: 99 | self.col_periods[self.last_col_period].extend(self.cols[(self.last_col):c]) 100 | else: 101 | self.col_periods[self.last_col_period] = copy.deepcopy(self.cols[(self.last_col):c]) 102 | self.last_col_period = period 103 | self.last_col = c 104 | 105 | def _add_row_period_explicit(self, period, row): 106 | if period in self.row_periods: 107 | self.row_periods[period].append(row) 108 | else: 109 | self.row_periods[period] = [row] 110 | 111 | def _add_col_period_explicit(self, period, col): 112 | if period in self.col_periods: 113 | self.col_periods[period].append(col) 114 | else: 115 | self.col_periods[period] = [col] 116 | 117 | # STOCH RELATED FUNCTIONS 118 | def _add_discrete_distrib(self, period, col, row, value, probability): 119 | key = tuple([col, row]) 120 | if period in self.distributions: 121 | if key in self.distributions[period]: 122 | self.distributions[period][key]["probabilities"][value] = probability 123 | else: 124 | self.distributions[period][key] = {"type": "DISCRETE", "probabilities": {value: probability}} 125 | else: 126 | self.distributions[period] = {key: {"type": "DISCRETE", "probabilities": {value: probability}}} 127 | 128 | def _add_distrib(self, period, col, row, distrib, params): 129 | key = tuple([col, row]) 130 | if period in self.distributions: 131 | if key in self.distributions[period]: 132 | raise ValueError("There were two different distributions given for the same element in one period!") 133 | else: 134 | self.distributions[period][key] = {"type": distrib} 135 | self.distributions[period][key].update(params) 136 | else: 137 | self.distributions[period] = {key: {"type": distrib}} 138 | self.distributions[period][key].update(params) 139 | 140 | def attach_block(self, period, block, _type, probability): 141 | if period not in self.blocks: 142 | self.blocks[period] = {} 143 | self.detach_block() 144 | 145 | self.curr_block_period = period 146 | self.curr_block = block 147 | if block not in self.blocks[period]: 148 | if _type == "DISCRETE": 149 | self.blocks[period][block] = {"type": _type, "probabilities": {}} 150 | else: 151 | self.blocks[period][block] = {"type": _type} 152 | self.new_block = True 153 | self.curr_elements = [] 154 | self.curr_basecase = [] 155 | else: 156 | self.new_block = False 157 | self.curr_elements = self.blocks[period][block]["elements"] 158 | self.curr_basecase = copy.copy(self.blocks[period][block]["basecase"]) 159 | self.curr_probability = probability 160 | 161 | 162 | def add_discrete_block_distrib(self, col, row, value): 163 | if self.new_block: 164 | self.curr_elements.append(tuple([col, row])) 165 | self.curr_basecase.append(value) 166 | else: 167 | self.curr_basecase[self.curr_elements.index(tuple([col, row]))] = value 168 | 169 | def add_sub_block_distrib(self, col, row): 170 | if self.curr_block: 171 | self.curr_elements.append(tuple([col, row])) 172 | 173 | def detach_block(self): 174 | if not self.curr_block: 175 | return 176 | if self.new_block: 177 | self.blocks[self.curr_block_period][self.curr_block]["elements"] = self.curr_elements 178 | for t in self.curr_elements: 179 | self.distributions[self.curr_block_period][t] = {"type": "BLOCK", "block": self.curr_block} 180 | if self.blocks[self.curr_block_period][self.curr_block]["type"] == "DISCRETE": 181 | if self.new_block: 182 | self.blocks[self.curr_block_period][self.curr_block]["basecase"] = self.curr_basecase 183 | self.blocks[self.curr_block_period][self.curr_block]["probabilities"][tuple(self.curr_basecase)] = self.curr_probability 184 | 185 | self.curr_block = None 186 | self.curr_block_period = None 187 | self.curr_basecase = None 188 | self.curr_probability = None 189 | self.curr_elements = None 190 | self.curr_basecase = None 191 | 192 | def attach_lintr(self, period, block, lintr, distrib, parameters): 193 | self.curr_lintr_name = lintr 194 | self.curr_lintr_period = period 195 | self.curr_lintr_block = block 196 | self.curr_lintr = {"type": distrib, "coefficients": {}} 197 | self.curr_lintr.update(parameters) 198 | 199 | def add_to_lintr(self, col, row, coeff): 200 | if self.curr_lintr_block: 201 | self.curr_lintr["coefficients"][tuple([col, row])] = coeff 202 | 203 | def detach_lintr(self): 204 | if not self.curr_lintr_name: 205 | return 206 | self.blocks[self.curr_lintr_period][self.curr_lintr_block][self.curr_lintr_name] = self.curr_lintr 207 | 208 | self.curr_lintr_period = None 209 | self.curr_lintr_block = None 210 | self.curr_lintr_name = None 211 | self.curr_lintr = None 212 | 213 | # used to produce dict 214 | def __iter__(self): 215 | for field in self.mps: 216 | yield field 217 | 218 | yield ("row_periods", self.row_periods) 219 | yield ("col_periods", self.col_periods) 220 | yield ("distributions", self.distributions) 221 | yield ("blocks", self.blocks) 222 | 223 | def _read_tim(smps, path): 224 | mps = smps.mps 225 | 226 | mode = -1 227 | 228 | explicit = False 229 | rows = smps.rows 230 | cols = smps.cols 231 | 232 | with open(path, "r") as reader: 233 | for line in reader: 234 | if line.startswith("*"): 235 | continue 236 | 237 | line = re.split(" |\t", line) 238 | line = [x.strip() for x in line] 239 | line = list(filter(None, line)) 240 | 241 | if len(line) == 0: 242 | continue 243 | if line[0] == "*": 244 | continue 245 | if line[0] == "ENDATA": 246 | break 247 | if line[0] == "TIME": 248 | assert line[1] == mps.name 249 | 250 | 251 | elif line[0] == TIME_FILE_PERIODS_MODE: 252 | if len(line) > 1 and line[1] == "EXPLICIT": 253 | mode = TIME_MODE[TIME_FILE_PERIODS_MODE_EXPLICIT] 254 | explicit = True 255 | else: # in case it is blank, IMPLICIT or anything else set it to implicit. 256 | mode = TIME_MODE[TIME_FILE_PERIODS_MODE_IMPLICIT] 257 | smps.start_implicit() 258 | 259 | 260 | elif line[0] == TIME_FILE_ROWS_MODE: 261 | mode = TIME_MODE[TIME_FILE_ROWS_MODE] 262 | 263 | 264 | elif line[0] == TIME_FILE_COLS_MODE: 265 | mode = TIME_MODE[TIME_FILE_COLS_MODE] 266 | 267 | 268 | elif mode == TIME_MODE[TIME_FILE_PERIODS_MODE_IMPLICIT]: 269 | smps._add_col_period_implicit(line[2], cols.index(line[0])) 270 | smps._add_row_period_implicit(line[2], rows.index(line[1])) 271 | 272 | 273 | elif mode == TIME_MODE[TIME_FILE_ROWS_MODE]: 274 | smps._add_row_period_explicit(line[1], line[0]) 275 | 276 | 277 | elif mode == TIME_FILE_COLS_MODE: 278 | smps._add_col_period_explicit(line[1], line[0]) 279 | 280 | 281 | smps.finalize_implicit() 282 | 283 | 284 | def _read_sto(smps, path): 285 | mps = smps.mps 286 | 287 | mode = -1 288 | distribution = None 289 | 290 | with open(path, "r") as reader: 291 | for line in reader: 292 | if line.startswith("*"): 293 | continue 294 | 295 | line = re.split(" |\t", line) 296 | line = [x.strip() for x in line] 297 | line = list(filter(None, line)) 298 | 299 | if len(line) == 0: 300 | continue 301 | if line[0] == "*": 302 | continue 303 | if line[0] == "ENDATA": 304 | break 305 | if line[0] == "STOCH": 306 | assert line[1] == mps.name 307 | 308 | 309 | elif line[0] == STOCH_FILE_INDEP_MODE: 310 | if line[1] == "DISCRETE": 311 | mode = STOCH_MODE[STOCH_FILE_INDEP_DISCRETE_MODE] 312 | elif line[1] in ["UNIFORM", "NORMAL", "BETA", "GAMMA", "LOGNORMAL"]: 313 | mode = STOCH_MODE[STOCH_FILE_INDEP_DISTRIB_MODE] 314 | elif line[1] == "SUB": 315 | mode = STOCH_MODE[STOCH_FILE_INDEP_SUB_MODE] 316 | warnings.warn(".sto file contains SUB section - this indicates that the user has to have a subroutine to generate distributions for a given row/col combination!") 317 | distribution = line[1] 318 | 319 | smps.detach_lintr() 320 | smps.detach_block() 321 | 322 | elif line[0] == STOCH_FILE_BLOCKS_MODE: 323 | if line[1] == "DISCRETE": 324 | mode = STOCH_MODE[STOCH_FILE_BLOCKS_DISCRETE_MODE] 325 | elif line[1] == "SUB": 326 | mode = STOCH_MODE[STOCH_FILE_BLOCKS_SUB_MODE] 327 | elif line[1] == "LINTR": 328 | mode = STOCH_MODE[STOCH_FILE_BLOCKS_LINTR_MODE] 329 | 330 | smps.detach_lintr() 331 | smps.detach_block() 332 | 333 | elif line[0] == STOCH_FILE_SCENARIOS_MODE: 334 | raise ValueError("SCENARIOS are currently not implemented!") 335 | 336 | 337 | elif mode == STOCH_MODE[STOCH_FILE_INDEP_DISCRETE_MODE]: 338 | smps._add_discrete_distrib(line[3], line[0], line[1], float(line[2]), float(line[4])) 339 | 340 | 341 | elif mode == STOCH_MODE[STOCH_FILE_INDEP_DISTRIB_MODE]: 342 | smps._add_distrib(line[3], line[0], line[1], distribution, {"parameters": [float(line[2]), float(line[4])]}) 343 | 344 | 345 | elif mode == STOCH_MODE[STOCH_FILE_INDEP_SUB_MODE]: 346 | smps._add_distrib(line[3], line[0], line[1], distribution, {}) 347 | 348 | 349 | elif mode == STOCH_MODE[STOCH_FILE_BLOCKS_DISCRETE_MODE]: 350 | if line[0] == "BL": 351 | smps.attach_block(line[2], line[1], "DISCRETE", float(line[3])) 352 | else: 353 | smps.add_discrete_block_distrib(line[0], line[1], float(line[2])) 354 | 355 | 356 | elif mode == STOCH_MODE[STOCH_FILE_BLOCKS_SUB_MODE]: 357 | if line[0] == "BL": 358 | smps.attach_block(line[2], line[1], "SUB", 0) 359 | else: 360 | smps.add_sub_block_distrib(line[0], line[1]) 361 | 362 | 363 | elif mode == STOCH_MODE[STOCH_FILE_BLOCKS_LINTR_MODE]: 364 | if line[0] == "BL": 365 | smps.detach_lintr() 366 | smps.attach_block(line[2], line[1], "LINTR", 0) 367 | elif line[0] == "RV": 368 | if smps.curr_block: 369 | period = smps.curr_block_period 370 | block = smps.curr_block 371 | else: 372 | period = smps.curr_lintr_period 373 | block = smps.curr_lintr_block 374 | smps.detach_block() 375 | smps.detach_lintr() 376 | if line[2] in ["UNIFORM", "NORMAL", "BETA", "GAMMA", "LOGNORMAL"]: 377 | smps.attach_lintr(period, block, line[1], line[2], {"parameters": [float(line[3]), float(line[5])]}) 378 | elif line[2] == "CONSTANT": 379 | smps.attach_lintr(period, block, line[1], line[2], {}) 380 | else: 381 | if smps.curr_block: 382 | smps.add_sub_block_distrib(line[0], line[1]) 383 | elif smps.curr_lintr_block: 384 | smps.add_to_lintr(line[0], line[1], float(line[2])) 385 | 386 | smps.detach_block() 387 | smps.detach_lintr() 388 | 389 | 390 | def read_smps(path): 391 | smps = SMPS(read_mps(path + ".cor")) 392 | _read_tim(smps, path + ".tim") 393 | _read_sto(smps, path + ".sto") 394 | return smps 395 | -------------------------------------------------------------------------------- /pysmps/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmaerte/pysmps/aacdfc26959a0b267b331860e1fb717d924b3a52/pysmps/test/__init__.py -------------------------------------------------------------------------------- /pysmps/test/case01: -------------------------------------------------------------------------------- 1 | ********************************************************************* 2 | * TEST CASE 01; TESTS IF A BASIC MPS PROBLEM IS READ CORRECTLY * 3 | * INCLUDES: * 4 | * * VARIABLES WITH TWO-SIDED BOUNDS * 5 | * * VARIABLES WITH SINGLE-SIDED BOUNDS BUT MULTIPLE * 6 | * "CONFLICTING" BOUNDS GIVEN * 7 | * * INTEGER VARIABLES * 8 | * * BINARY VARIABLES * 9 | * * FREE VARIABLES * 10 | * * FIXED VARIABLES * 11 | * * LEQ, GEQ, EQ CONSTRAINTS * 12 | * * 13 | ********************************************************************* 14 | 15 | NAME test_case01 16 | ROWS 17 | N obj 18 | L leq 19 | E eq 20 | G geq 21 | 22 | COLUMNS 23 | c_bnd leq 5.000000000000e+00 24 | c_bnd eq 2.100000000000e+01 25 | c_bnd geq 1.800000000000e+01 26 | c_bnd obj 1.500000000000e+00 27 | 28 | c_bnd2 eq -1.000000000000e+01 29 | c_bnd2 obj 2.500000000000e+00 30 | 31 | c_bnd3 leq -1.000000000000e+01 32 | c_bnd3 obj -3.500000000000e+00 33 | 34 | c_bnd_up leq -1.000000000000e+00 35 | c_bnd_up geq -5.000000000000e+00 36 | c_bnd_up obj -4.500000000000e+00 37 | 38 | c_bnd_lo leq 1.000000000000e+00 39 | c_bnd_lo eq 3.000000000000e+00 40 | c_bnd_lo obj 5.500000000000e+00 41 | 42 | MARK 'MARKER' 'INTORG' 43 | int leq 1.500000000000e+00 44 | int eq 2.500000000000e+00 45 | int geq -3.500000000000e+00 46 | int obj 9.000000000000e+00 47 | MARK 'MARKER' 'INTEND' 48 | 49 | bin leq 1.500000000000e+02 50 | bin obj 9.500000000000e+00 51 | 52 | free geq 2.500000000000e+02 53 | free obj -1.205000000000e+02 54 | 55 | fixed obj 1.205000000000e+02 56 | RHS 57 | RHS leq -5.000000000000e+01 58 | RHS eq 1.000000000000e+02 59 | RHS geq -8.500000000000e+01 60 | BOUNDS 61 | LO BND c_bnd -5.000000000000e+00 62 | UP BND c_bnd 1.000000000000e+01 63 | 64 | LO BND c_bnd2 5.000000000000e+00 65 | UP BND c_bnd2 1.000000000000e+02 66 | 67 | LO BND c_bnd3 -1.000000000000e+02 68 | UP BND c_bnd3 -5.000000000000e+00 69 | 70 | UP BND c_bnd_up 5.000000000000e+00 71 | UP BND c_bnd_up 1.000000000000e+01 72 | 73 | LO BND c_bnd_lo -5.000000000000e+00 74 | LO BND c_bnd_lo -1.000000000000e+01 75 | 76 | LO BND int 5.000000000000e+00 77 | UP BND int 1.000000000000e+01 78 | 79 | BV BND bin 80 | 81 | FR BND free 82 | 83 | FX BND fixed 2.050000000000e+01 84 | ENDATA 85 | -------------------------------------------------------------------------------- /pysmps/test/case02: -------------------------------------------------------------------------------- 1 | ********************************************************************* 2 | * TEST CASE 01; TESTS IF A BASIC MPS PROBLEM IS READ CORRECTLY * 3 | * INCLUDES: * 4 | * * HAVING MULTIPLE BOUNDS/RHS COMBINATIONS * 5 | * * 6 | ********************************************************************* 7 | 8 | NAME test_case02 9 | ROWS 10 | N obj 11 | L leq 12 | 13 | COLUMNS 14 | c_bnd leq 5.000000000000e+00 15 | c_bnd obj 1.500000000000e+00 16 | 17 | c_bnd2 obj 2.500000000000e+00 18 | 19 | c_bnd3 leq -1.000000000000e+01 20 | c_bnd3 obj -3.500000000000e+00 21 | 22 | c_bnd_up leq -1.000000000000e+00 23 | c_bnd_up obj -4.500000000000e+00 24 | 25 | c_bnd_lo leq 1.000000000000e+00 26 | c_bnd_lo obj 5.500000000000e+00 27 | 28 | MARK 'MARKER' 'INTORG' 29 | int leq 1.500000000000e+00 30 | int obj 9.000000000000e+00 31 | MARK 'MARKER' 'INTEND' 32 | 33 | bin leq 1.500000000000e+02 34 | bin obj 9.500000000000e+00 35 | 36 | free obj -1.205000000000e+02 37 | 38 | fixed obj 1.205000000000e+02 39 | RHS 40 | RHS1 leq -5.000000000000e+01 41 | RHS2 leq 5.000000000000e+01 42 | BOUNDS 43 | LO BND c_bnd -5.000000000000e+00 44 | UP BND c_bnd 1.000000000000e+01 45 | 46 | LO BND c_bnd2 5.000000000000e+00 47 | UP BND c_bnd2 1.000000000000e+02 48 | 49 | LO BND c_bnd3 -1.000000000000e+02 50 | UP BND c_bnd3 -5.000000000000e+00 51 | 52 | UP BND c_bnd_up 5.000000000000e+00 53 | UP BND c_bnd_up 1.000000000000e+01 54 | 55 | LO BND c_bnd_lo -5.000000000000e+00 56 | LO BND c_bnd_lo -1.000000000000e+01 57 | 58 | LO BND int 5.000000000000e+00 59 | UP BND int 1.000000000000e+01 60 | 61 | BV BND bin 62 | 63 | FR BND free 64 | 65 | FX BND fixed 2.050000000000e+01 66 | ENDATA -------------------------------------------------------------------------------- /pysmps/test/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import math 3 | 4 | import os,sys,inspect 5 | current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 6 | parent_dir = os.path.dirname(current_dir) 7 | sys.path.insert(0, parent_dir) 8 | from mps_loader import read_mps 9 | 10 | class TestMPSReader(unittest.TestCase): 11 | 12 | def test_case01(self): 13 | self.maxDiff=None 14 | self.assertDictEqual( 15 | dict(read_mps(current_dir + "/case01")), 16 | {'name': 'test_case01', 17 | 'objective_names': ['obj'], 18 | 'objectives': { 19 | 'obj': 20 | {'coefficients': { 21 | 'c_bnd': 1.5, 22 | 'c_bnd2': 2.5, 23 | 'c_bnd3': -3.5, 24 | 'c_bnd_up': -4.5, 25 | 'c_bnd_lo': 5.5, 26 | 'int': 9.0, 27 | 'bin': 9.5, 28 | 'free': -120.5, 29 | 'fixed': 120.5 30 | } 31 | } 32 | }, 33 | 'bnd_names': ['BND'], 34 | 'rhs_names': ['RHS'], 35 | 'variables': { 36 | 'BND': { 37 | 'c_bnd': { 38 | 'type': 'Continuous', 39 | 'lower': -5.0, 40 | 'upper': 10.0 41 | }, 42 | 'c_bnd2': { 43 | 'type': 'Continuous', 44 | 'lower': 5.0, 45 | 'upper': 100.0 46 | }, 47 | 'c_bnd3': { 48 | 'type': 'Continuous', 49 | 'lower': -100.0, 50 | 'upper': -5.0 51 | }, 52 | 'c_bnd_up': { 53 | 'type': 'Continuous', 54 | 'lower': 0.0, 55 | 'upper': 10.0 56 | }, 57 | 'c_bnd_lo': { 58 | 'type': 'Continuous', 59 | 'lower': -10.0, 60 | 'upper': math.inf 61 | }, 62 | 'int': { 63 | 'type': 'Integer', 64 | 'lower': 5.0, 65 | 'upper': 10.0 66 | }, 67 | 'bin': { 68 | 'type': 'Integer', 69 | 'lower': 0.0, 70 | 'upper': 1.0 71 | }, 72 | 'free': { 73 | 'type': 'Continuous', 74 | 'lower': -math.inf, 75 | 'upper': math.inf 76 | }, 77 | 'fixed': { 78 | 'type': 'Continuous', 79 | 'lower': 20.5, 80 | 'upper': 20.5 81 | } 82 | } 83 | }, 84 | 'constraint_names': ['leq', 'eq', 'geq'], 85 | 'constraints': { 86 | 'leq': { 87 | 'type': 'L', 88 | 'coefficients': {'c_bnd': 5.0, 'c_bnd3': -10.0, 'c_bnd_up': -1.0, 'c_bnd_lo': 1.0, 'int': 1.5, 'bin': 150.0} 89 | }, 90 | 'eq': { 91 | 'type': 'E', 92 | 'coefficients': {'c_bnd': 21.0, 'c_bnd2': -10.0, 'c_bnd_lo': 3.0, 'int': 2.5} 93 | }, 94 | 'geq': { 95 | 'type': 'G', 96 | 'coefficients': {'c_bnd': 18.0, 'c_bnd_up': -5.0, 'int': -3.5, 'free': 250.0} 97 | } 98 | }, 99 | 'rhs': { 100 | 'RHS': { 101 | 'leq': -50.0, 102 | 'eq': 100.0, 103 | 'geq': -85.0 104 | } 105 | }, 106 | 'offsets': {'RHS': {'obj': 0}} 107 | }) 108 | 109 | def test_case02(self): 110 | self.assertDictEqual( 111 | read_mps(current_dir + "/case02"), 112 | {'objective': { 113 | 'name': 'obj', 114 | 'coefficients': [ 115 | {'name': 'c_bnd', 'value': 1.5}, 116 | {'name': 'c_bnd2', 'value': 2.5}, 117 | {'name': 'c_bnd3', 'value': -3.5}, 118 | {'name': 'c_bnd_up', 'value': -4.5}, 119 | {'name': 'c_bnd_lo', 'value': 5.5}, 120 | {'name': 'int', 'value': 9.0}, 121 | {'name': 'bin', 'value': 9.5}, 122 | {'name': 'free', 'value': -120.5}, 123 | {'name': 'fixed', 'value': 120.5} 124 | ] 125 | }, 126 | 'variables': { 127 | 'c_bnd': { 128 | 'type': 'Continuous', 129 | 'name': 'c_bnd', 130 | 'bnd_lower': -5.0, 131 | 'bnd_upper': 10.0 132 | }, 133 | 'c_bnd2': { 134 | 'type': 'Continuous', 135 | 'name': 'c_bnd2', 136 | 'bnd_lower': 5.0, 137 | 'bnd_upper': 100.0 138 | }, 139 | 'c_bnd3': { 140 | 'type': 'Continuous', 141 | 'name': 'c_bnd3', 142 | 'bnd_lower': -100.0, 143 | 'bnd_upper': -5.0 144 | }, 145 | 'c_bnd_up': { 146 | 'type': 'Continuous', 147 | 'name': 'c_bnd_up', 148 | 'bnd_lower': 0, 149 | 'bnd_upper': 5.0 150 | }, 151 | 'c_bnd_lo': { 152 | 'type': 'Continuous', 153 | 'name': 'c_bnd_lo', 154 | 'bnd_lower': -5.0, 155 | 'bnd_upper': math.inf 156 | }, 157 | 'int': { 158 | 'type': 'Integer', 159 | 'name': 'int', 160 | 'bnd_lower': 5.0, 161 | 'bnd_upper': 10.0 162 | }, 163 | 'bin': { 164 | 'type': 'Integer', 165 | 'name': 'bin', 166 | 'bnd_lower': 0.0, 167 | 'bnd_upper': 1.0 168 | }, 169 | 'free': { 170 | 'type': 'Continuous', 171 | 'name': 'free', 172 | 'bnd_lower': -math.inf, 173 | 'bnd_upper': math.inf 174 | }, 175 | 'fixed': { 176 | 'type': 'Continuous', 177 | 'name': 'fixed', 178 | 'bnd_lower': 20.5, 179 | 'bnd_upper': 20.5 180 | } 181 | }, 182 | 'constraints': { 183 | 'leq': { 184 | 'type': 'L', 185 | 'name': 'leq', 186 | 'coefficients': [{'name': 'c_bnd', 'value': 5.0}, {'name': 'c_bnd3', 'value': -10.0}, {'name': 'c_bnd_up', 'value': -1.0}, {'name': 'c_bnd_lo', 'value': 1.0}, {'name': 'int', 'value': 1.5}, {'name': 'bin', 'value': 150.0}], 187 | 'bounds': {'RHS1': -50.0, 'RHS2': 50.0} 188 | } 189 | }, 190 | 'rhs_names': ['RHS1', 'RHS2'] 191 | }) 192 | 193 | class TestMPSWriter(unittest.TestCase): 194 | 195 | def test_case01(self): 196 | return 197 | 198 | if __name__ == '__main__': 199 | unittest.main() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.12.1 -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==19.2.3 2 | wheel==0.33.1 3 | twine==1.13.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open('requirements.txt', "r") as reader: 4 | required = reader.read().splitlines() 5 | 6 | with open("PYPI_README.md", "r") as readme_file: 7 | readme = readme_file.read() 8 | 9 | setup( 10 | name='pysmps', 11 | version='1.6', 12 | description='Utilities for parsing MPS and SMPS file formats.', 13 | long_description=readme, 14 | long_description_content_type="text/markdown", 15 | author='Julian Maerte', 16 | author_email='maertej@students.uni-marburg.de', 17 | packages=find_packages(), 18 | url="https://github.com/jmaerte/pysmps", 19 | install_required=required, 20 | ) 21 | --------------------------------------------------------------------------------