Source code for sasoptpy.util.user_utils

#!/usr/bin/env python
# encoding: utf-8
#
# Copyright SAS Institute
#
#  Licensed under the Apache License, Version 2.0 (the License);
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#

"""
User accessible utility functions
"""

from collections import Iterable
import random
import string
import warnings
from contextlib import contextmanager

import sasoptpy
from sasoptpy.libs import (pd, np)
from .package_utils import (
    wrap_expression, _wrap_expression_with_iterators,
    get_first_member, pack_to_tuple)


def concat(exp1, exp2):
    return wrap_expression(exp1).concat(wrap_expression(exp2))


@contextmanager
def iterate(set, name):
    if not sasoptpy.abstract.is_abstract_set(set):
        set = sasoptpy.Set.from_object(set)
    if isinstance(name, list):
        yield sasoptpy.abstract.SetIteratorGroup(set, names=name)
    else:
        yield sasoptpy.abstract.SetIterator(set, name=name)


[docs]def exp_range(start, stop, step=1): """ Creates a set within specified range Parameters ---------- start : :class:`Expression` First value of the range stop : :class:`Expression` Last value of the range step : :class:`Expression`, optional Step size of the range Returns ------- exset : Set Set that represents the range Examples -------- >>> N = so.Parameter(name='N') >>> p = so.exp_range(1, N) >>> print(p._defn()) set 1..N; """ regular = isinstance(start, int) and isinstance(stop, int) and\ isinstance(step, int) if regular: return range(start, stop, step) s = sasoptpy.abstract.AbstractRange(start, stop, step) return s
def get_solution_table(*argv, key=None, rhs=False): return get_value_table(*argv, key=key, rhs=rhs)
[docs]def get_value_table(*args, **kwargs): """ Returns values of the given arguments as a merged pandas DataFrame Parameters ---------- key : list, optional Keys for objects rhs : bool, optional Option for including constant values Returns ------- table : :class:`pandas.DataFrame` DataFrame object that holds object values """ if len(args) == 0: return None series = [] for arg in args: s = get_values(arg, **kwargs) series.append(s) return pd.concat(series, axis=1, sort=False)
def get_values(arg, **kwargs): """ Returns values of given set of arguments as a pandas Series """ if isinstance(arg, pd.Series): arg_values = arg.apply( lambda row: row.get_value() if hasattr(row, 'get_value') else str(row)) return arg_values elif isinstance(arg, pd.DataFrame): arg_values = arg.apply( lambda col: col.apply( lambda row: row.get_value() if hasattr(row, 'get_value') else str(row))) return arg_values elif isinstance(arg, sasoptpy.VariableGroup): keys = [] values = [] members = arg.get_members() if arg._abstract is False else arg.get_shadow_members() for i in members: if any([sasoptpy.abstract.is_abstract(j) for j in i]): #if sasoptpy.abstract.is_abstract(get_first_member(i)): continue keys.append(get_first_member(i)) values.append(members[i].get_value()) return pd.Series(values, index=keys, name=arg.get_name()) elif isinstance(arg, sasoptpy.ConstraintGroup): keys = [] values = [] members = arg.get_all_keys() rhs = kwargs.get('rhs', False) for i in members: if sasoptpy.abstract.is_abstract(get_first_member(i)): continue keys.append(get_first_member(i)) values.append(arg[i].get_value(rhs=rhs)) return pd.Series(values, index=keys, name=arg.get_name()) elif isinstance(arg, sasoptpy.ImplicitVar): keys = [] values = [] members = arg.get_dict() for i in members: keys.append(get_first_member(i)) values.append(members[i].get_value()) return pd.Series(values, index=keys, name=arg.get_name()) elif isinstance(arg, sasoptpy.Expression): return pd.Series([arg.get_value()], index=['-'], name=arg.get_name()) else: return pd.Series([arg], index=['-'], name=str(arg)) def submit(caller, *args, **kwargs): session = caller.get_session() session_type = sasoptpy.util.package_utils.get_session_type(session) if session is None or session_type is None: raise RuntimeError('No session is available') mediator_class = sasoptpy.mediators[session_type] mediator = mediator_class(caller, session) return mediator.submit(*args, **kwargs) def submit_for_solve(caller, *args, **kwargs): session = caller.get_session() session_type = sasoptpy.util.package_utils.get_session_type(session) if session is None or session_type is None: raise RuntimeError('No session is available') mediator_class = sasoptpy.mediators[session_type] mediator = mediator_class(caller, session) return mediator.solve(*args, **kwargs) def submit_for_tune(caller, **kwargs): session = caller.get_session() session_type = sasoptpy.util.package_utils.get_session_type(session) if session_type != 'CAS': raise TypeError('Tune action is only available on CAS (SAS Viya) servers.') mediator_class= sasoptpy.mediators[session_type] mediator = mediator_class(caller, session) return mediator.tune(**kwargs)
[docs]def quick_sum(argv): """ Summation function for :class:`Expression` objects Notes ----- This method will deprecate in future versions. Use :func:`expr_sum` instead. """ return sasoptpy.util.expr_sum(argv)
[docs]def expr_sum(argv): """ Summation function for :class:`Expression` objects Returns ------- exp : :class:`Expression` Sum of given arguments Examples -------- >>> x = so.VariableGroup(10000, name='x') >>> y = so.expr_sum(2*x[i] for i in range(10000)) Notes ----- This function is faster for expressions compared to Python's native sum() function. """ clocals = argv.gi_frame.f_locals.copy() exp = sasoptpy.core.Expression() exp.set_temporary() iterators = [] for i in argv: exp = exp + i if sasoptpy.core.util.is_expression(i): #if i._abstract: newlocals = argv.gi_frame.f_locals for nl in newlocals.keys(): if nl not in clocals and\ (type(newlocals[nl]) == sasoptpy.abstract.SetIterator or type(newlocals[nl]) == sasoptpy.abstract.SetIteratorGroup): iterators.append(newlocals[nl]) newlocals[nl].set_name(nl) if iterators: iterators = sorted(iterators, key=lambda i: i._objorder) exp = _wrap_expression_with_iterators(exp, 'sum', iterators) exp.set_permanent() return exp
[docs]def reset(): """ Resets package configs and internal counters """ sasoptpy.config.reset() sasoptpy.itemid = 0
def reset_globals(): warnings.warn('Use sasoptpy.reset()', DeprecationWarning) reset()
[docs]def dict_to_frame(dictobj, cols=None): """ Converts dictionaries to DataFrame objects for pretty printing Parameters ---------- dictobj : dict Dictionary to be converted cols : list, optional Column names Returns ------- frobj : DataFrame DataFrame representation of the dictionary Examples -------- >>> d = {'coal': {'period1': 1, 'period2': 5, 'period3': 7}, >>> 'steel': {'period1': 8, 'period2': 4, 'period3': 3}, >>> 'copper': {'period1': 5, 'period2': 7, 'period3': 9}} >>> df = so.dict_to_frame(d) >>> print(df) period1 period2 period3 coal 1 5 7 copper 5 7 9 steel 8 4 3 """ frobj = pd.convert_dict_to_frame(dictobj, cols) return frobj
[docs]def flatten_frame(df, swap=False): """ Converts a :class:`pandas.DataFrame` object into :class:`pandas.Series` Parameters ---------- df : :class:`pandas.DataFrame` DataFrame to be flattened swap : boolean, optional Option to use columns as first index Returns ------- new_frame : :class:`pandas.DataFrame` A new DataFrame where indices consist of index and columns names as tuples Examples -------- >>> price = pd.DataFrame([ >>> [1, 5, 7], >>> [8, 4, 3], >>> [5, 7, 9]], columns=[\'period1\', \'period2\', \'period3\']).\\ >>> set_index([[\'coal\', \'steel\', \'copper\']]) >>> print(\'Price data: \\n{}\'.format(price)) >>> price_f = so.flatten_frame(price) >>> print(\'Price data: \\n{}\'.format(price_f)) Price data: period1 period2 period3 coal 1 5 7 steel 8 4 3 copper 5 7 9 Price data: (coal, period1) 1 (coal, period2) 5 (coal, period3) 7 (steel, period1) 8 (steel, period2) 4 (steel, period3) 3 (copper, period1) 5 (copper, period2) 7 (copper, period3) 9 dtype: int64 """ new_frame = df.stack() if swap: new_frame = new_frame.swaplevel() #new_frame.index = new_frame.index.to_series() return new_frame
def is_linear(item): return item._is_linear() def has_integer_variables(item): return item._has_integer_vars() def export_to_mps(model, filename=None): mps = model.to_mps() # Convert to standard MPS format mps._set_value(0, 'Field2', mps.loc[0, 'Field3']) mps._set_value(0, 'Field3', '') mps._set_value(0, 'Field4', np.nan) mps._set_value(0, 'Field6', np.nan) mps._set_value(len(mps)-1, 'Field4', np.nan) mps._set_value(len(mps)-1, 'Field6', np.nan) mps.drop(columns="_id_", inplace=True) def add_space(r): keywords = ['NAME', 'ROWS', 'COLUMNS', 'RHS', 'BOUNDS', 'RANGES', 'ENDATA'] if r not in keywords: return " " + r else: return r mps['Field1'] = mps['Field1'].apply(add_space) formatters={ 'Field1': '{{:<{}s}}'.format(mps['Field1'].str.len().max()).format, 'Field2': '{{:<{}s}}'.format(mps['Field2'].str.len().max()).format, 'Field3': '{{:<{}s}}'.format(mps['Field3'].str.len().max()).format, 'Field5': '{{:<{}s}}'.format(mps['Field5'].str.len().max()).format, } mps_str = mps.to_string(formatters=formatters, index=False, header=False, na_rep='') if mps_str[0] == " ": mps_str = '\n'.join([i[1:] for i in mps_str.split('\n')]) if filename is not None: with open(filename, 'w') as file: file.write(mps_str) return mps_str