Handling Data

sasoptpy can work with native Python types and pandas objects for all data operations. Among pandas object types, sasoptpy works with pandas.DataFrame and pandas.Series objects to construct and manipulate model components.

Indices

Methods like Model.add_variables() can use native Python object types such as list and range as variable and constraint indices. You can also use pandas.Index objects as indices as well.

List

In [1]: m = so.Model(name='demo')
NOTE: Initialized model demo.

In [2]: SEASONS = ['Fall', 'Winter', 'Spring', 'Summer']

In [3]: prod_lb = {'Fall': 100, 'Winter': 200, 'Spring': 100, 'Summer': 400}

In [4]: production = m.add_variables(SEASONS, lb=prod_lb, name='production')

In [5]: print(production)
Variable Group (production) [
  [Fall: production[Fall]]
  [Winter: production[Winter]]
  [Spring: production[Spring]]
  [Summer: production[Summer]]
]
In [6]: print(repr(production['Summer']))
sasoptpy.Variable(name='production[Summer]', lb=400, vartype='CONT')

If a list is used as the index set, associated fields such as lb, and ub should be accessible by using the index keys. Accepted types are dict and pandas.Series.

Range

In [7]: link = m.add_variables(range(3), range(2), vartype=so.BIN, name='link')

In [8]: print(link)
Variable Group (link) [
  [(0, 0): link[0, 0]]
  [(0, 1): link[0, 1]]
  [(1, 0): link[1, 0]]
  [(1, 1): link[1, 1]]
  [(2, 0): link[2, 0]]
  [(2, 1): link[2, 1]]
]
In [9]: print(repr(link[2, 1]))
sasoptpy.Variable(name='link[2,1]', lb=0, ub=1, vartype='BIN')

pandas.Index

In [10]: import pandas as pd

In [11]: p_data = [[3, 5, 9],
   ....:           [0, -1, 14],
   ....:           [5, 6, 20]]
   ....: 

In [12]: df = pd.DataFrame(p_data, columns=['c1', 'col_lb', 'col_ub'])

In [13]: x = m.add_variables(df.index, lb=df['c1'], vartype=so.INT, name='x')
In [14]: print(x)
Variable Group (x) [
  [0: x[0]]
  [1: x[1]]
  [2: x[2]]
]
In [15]: df2 = df.set_index([['r1', 'r2', 'r3']])

In [16]: y = m.add_variables(df2.index, lb=df2['col_lb'], ub=df2['col_ub'], name='y')
In [17]: print(y)
Variable Group (y) [
  [r1: y[r1]]
  [r2: y[r2]]
  [r3: y[r3]]
]
In [18]: print(repr(y['r1']))
sasoptpy.Variable(name='y[r1]', lb=5, ub=9, vartype='CONT')

Set

sasoptpy can work with data on the server and generate abstract expressions. For this purpose, you can use Set objects to represent PROC OPTMODEL sets.

In [19]: m2 = so.Model(name='m2')
NOTE: Initialized model m2.

In [20]: I = m2.add_set(name='I')

In [21]: u = m2.add_variables(I, name='u')

In [22]: print(I, u)
I Variable Group (u) [
]

See Workflows for more information about working with server-side models.

Data

sasoptpy can work with both client-side and server-side data. Here are some options to load data into the optimization models.

pandas DataFrame

pandas.DataFrame is the preferred object type for passing data into sasoptpy models.

In [23]: data = [
   ....:    ['clock', 8, 4, 3],
   ....:    ['mug', 10, 6, 5],
   ....:    ['headphone', 15, 7, 2],
   ....:    ['book', 20, 12, 10],
   ....:    ['pen', 1, 1, 15]
   ....:    ]
   ....: 

In [24]: df = pd.DataFrame(data, columns=['item', 'value', 'weight', 'limit']).set_index(['item'])

In [25]: get = so.VariableGroup(df.index, ub=df['limit'], name='get')

In [26]: print(get)
Variable Group (get) [
  [clock: get[clock]]
  [mug: get[mug]]
  [headphone: get[headphone]]
  [book: get[book]]
  [pen: get[pen]]
]

Dictionaries

You can use lists and dictionaries in expressions and when you create variables.

In [27]: items = ['clock', 'mug', 'headphone', 'book', 'pen']

In [28]: limits = {'clock': 3, 'mug': 5, 'headphone': 2, 'book': 10, 'pen': 15}

In [29]: get2 = so.VariableGroup(items, ub=limits, name='get2')

In [30]: print(get2)
Variable Group (get2) [
  [clock: get2[clock]]
  [mug: get2[mug]]
  [headphone: get2[headphone]]
  [book: get2[book]]
  [pen: get2[pen]]
]

CASTable

When data are available on the server-side, you can pass a reference to the object. Using swat.cas.table.CASTable and abstract data requires SAS Viya 3.4 or later.

In [31]: m2 = so.Model(name='m2', session=session)
NOTE: Initialized model m2.
In [32]: table = session.upload_frame(df)
NOTE: Cloud Analytic Services made the uploaded file available as table TMP4I8FIETC in caslib CASUSER(casuser).
NOTE: The table TMP4I8FIETC has been created in caslib CASUSER(casuser) from binary data uploaded to Cloud Analytic Services.
In [33]: print(type(table), table)
<class 'swat.cas.table.CASTable'> CASTable('TMP4I8FIETC', caslib='CASUSER(casuser)')
In [34]: df = pd.DataFrame(data, columns=['item', 'value', 'weight', 'limit'])

In [35]: ITEMS = m.add_set(name='ITEMS')

In [36]: value = m.add_parameter(ITEMS, name='value')

In [37]: weight = m.add_parameter(ITEMS, name='weight')

In [38]: limit = m.add_parameter(ITEMS, name='limit')

In [39]: from sasoptpy.actions import read_data

In [40]: m.include(read_data(table=table, index={'target': ITEMS, 'key': None},
   ....:                          columns=[value, weight, limit]))
   ....: 

In [41]: get3 = m2.add_variables(ITEMS, name='get3')

In [42]: print(get3)
Variable Group (get3) [
]

Abstract Data

If you would like to model your problem first and load data later, you can pass a string for the data that will be available later.

In [43]: from sasoptpy.actions import read_data

In [44]: m3 = so.Model(name='m3', session=session)
NOTE: Initialized model m3.

In [45]: ITEMS = m.add_set(name='ITEMS')

In [46]: limit = m.add_parameter(ITEMS, name='limit')

In [47]: m3.include(read_data(table='DF', index=['item'], columns=[limit]))

In [48]: print(type(ITEMS), ITEMS)
<class 'sasoptpy.abstract.set.Set'> ITEMS

Note that the key set is created as a reference. You can solve the problem later after having the data available with the same name (for example, by using the swat.cas.connection.CAS.upload_frame() function)

In [49]: session.upload_frame(df, casout='DF')
NOTE: Cloud Analytic Services made the uploaded file available as table DF in caslib CASUSER(casuser).
NOTE: The table DF has been created in caslib CASUSER(casuser) from binary data uploaded to Cloud Analytic Services.
Out[49]: CASTable('DF', caslib='CASUSER(casuser)')

Operations

You can use lists, pandas.Series, and pandas.DataFrame objects for mathematical operations such as VariableGroup.mult().

In [50]: sd = [3, 5, 6]

In [51]: z = m.add_variables(3, name='z')
In [52]: print(z)
Variable Group (z) [
  [0: z[0]]
  [1: z[1]]
  [2: z[2]]
]
In [53]: print(repr(z))
sasoptpy.VariableGroup([0, 1, 2], name='z')
In [54]: e1 = z.mult(sd)

In [55]: print(e1)
3 * z[0] + 5 * z[1] + 6 * z[2]
In [56]: ps = pd.Series(sd)

In [57]: e2 = z.mult(ps)

In [58]: print(e2)
3 * z[0] + 5 * z[1] + 6 * z[2]