# ModelicaRes Advanced Topics¶

This IPython notebook demonstrates some of the advanced features and use cases of ModelicaRes.

First, we'll load the ModelicaRes classes we'll need:

In [1]:
from modelicares import SimRes, SimResList


and some standard modules and settings for this IPython notebook:

In [2]:
import numpy as np
import matplotlib.pyplot as plt
from pandas import DataFrame
%matplotlib inline
%precision 3

Out[2]:
u'%.3f'

## Sankey diagrams¶

SimRes has a built-in method (sankey) to produce Sankey diagrams. We'll plot subfigures with the Sankey diagrams of ThreeTanks at several times over the simulation:

In [3]:
sim = SimRes('ThreeTanks.mat')
sim.sankey(title="Sankey Diagrams of Modelica.Fluid.Examples.Tanks.ThreeTanks",
times=[0, 50, 100, 150], n_rows=2, format='%.1f ',
names=['tank1.ports[1].m_flow', 'tank2.ports[1].m_flow',
'tank3.ports[1].m_flow'],
labels=['Tank 1', 'Tank 2', 'Tank 3'],
orientations=[-1, 0, 1],
scale=0.1, margin=6, offset=1.5,
pathlengths=2, trunklength=10);

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-e512935140ce> in <module>()
----> 1 sim = SimRes('ThreeTanks.mat')
2 sim.sankey(title="Sankey Diagrams of Modelica.Fluid.Examples.Tanks.ThreeTanks",
3            times=[0, 50, 100, 150], n_rows=2, format='%.1f ',
4            names=['tank1.ports[1].m_flow', 'tank2.ports[1].m_flow',
5                   'tank3.ports[1].m_flow'],

/usr/local/lib/python2.7/dist-packages/ModelicaRes-0.12.2_96_gcc11e6e_dirty-py2.7.egg/modelicares/simres.pyc in __init__(self, fname, constants_only, tool)
931                                   '("%s").' % (tool,
932                                                '", "'.join(list(readerdict))))
--> 933         variables = read(fname, constants_only)
934         self.update(variables)
935

/usr/local/lib/python2.7/dist-packages/ModelicaRes-0.12.2_96_gcc11e6e_dirty-py2.7.egg/modelicares/_io/dymola.pyc in readsim(fname, constants_only)
348         for description, [data_set, sign_col] in zip(data['description'],
349                                                      data['dataInfo'][:, 0:2]):
--> 350             unit, display_unit, description = parse_description(description)
351             negated = sign_col < 0
352             traj = trajectories[data_set - 1]

/usr/local/lib/python2.7/dist-packages/ModelicaRes-0.12.2_96_gcc11e6e_dirty-py2.7.egg/modelicares/_io/dymola.pyc in parse_description(description)
305
306         display_unit = displayUnit if displayUnit else unit
--> 307         unit = U._units(**Exponents(unit)) # TODO: skip if units included
308         description = description.rstrip()
309         if PY2:

/usr/local/lib/python2.7/dist-packages/natu-0.1.0_b_7_gfb200b7_dirty-py2.7.egg/natu/core.pyc in __call__(self, *args, **factors)
1378             factors = UnitExponents(args[0])
1379         factors = [self[base] ** exp for base, exp in factors.items()]
-> 1380         return reduce(lambda x, y: x * y, factors)
1381
1382     def load_ini(self, files):

TypeError: reduce() of empty sequence with no initial value

Unfortunately, the formatting arguments (scale, margin, offset, pathlengths, and trunklength) usually require manual adjustment.

## Testing simulations based on criteria¶

As demonstrated in the tutorial, ModelicaRes has a special class for lists of simulation results. We'll use it to load a group of files by wildcard:

In [ ]:
sims = SimResList('*.mat', '*/*/*.mat')


Let's see which simulations are in the list:

In [ ]:
print(sims)


The directory contained some linearization results, but they were excluded automatically.

Now let's see which variables are available:

In [ ]:
sims.names


Only time is available! names() only returns the variables that the simulations have in common, and there are no others. We'll address that by filtering the list of simulations. To do so, we'll first introduce the concept of a test condition. We'll create a test that checks if a simulation has a variable named "L.L" (indicating that an inductor "L" is at the base of the model):

In [ ]:
has_inductor = lambda sim: 'L.L' in sim


Let's see if each simulation passes the test:

In [ ]:
[has_inductor(sim) for sim in sims]


There's actually another way to access the same information: unique_names(). It returns a dictionary of the variable names that aren't in all of the simulations. The value of each entry is a Boolean list indicating if the variable is in each simulation. For example, we can do:

In [ ]:
sims.unique_names['L.L']


Anyway, let's go ahead and filter our simulation list to those that have the inductor:

In [ ]:
sims = SimResList(filter(has_inductor, sims))
print(sims)


The ThreeTanks example has been removed. Now the names method returns so many variables that we'll use a pattern to limit it:

In [ ]:
sims.find('L*v')


In this case, we could have excluded the ThreeTanks example in the first place, but there may be situations where we have a directory of simulation results that we need to filter. We could have also used a test condition that looks at the values of constants, e.g.,

In [ ]:
has_small_inductance = lambda sim: 'L.L' in sim and sim['L.L'].value() < 14


This idea isn't limited to Boolean test functions. We could just as easily map a cost function to a list of simulations.

## Indexing lists of simulations¶

As mentioned in the tutorial, the get item method can be used on a SimResList of simulations to retrieve an attribute across all of the simulations, e.g.,

In [ ]:
sims['L.L'].value()


However, the method is overloaded so that it can still be used to index a simulation from the list of simulations:

In [ ]:
sims[0]['L.L'].value()


In this case, the first index was for the simulation result (a SimRes instance) and the second was for the variable within the result.

The in operator is also overloaded. It can be used to check if a variable is in all of the simulations:

In [ ]:
'L.L' in sims


or if a simulation is in the list of simulations:

In [ ]:
sims[0] in sims


However, it is not appropriate to overload some Python list methods in this context. Those that are not overloaded (e.g., insert, remove, pop, index, and count) only accept integer indices or the actual list elements (SimRes instances) as applicable, not variable names.

It is possible to use slices to extract a SimResList with selected simulations:

In [ ]:
print(sims[:2])


Compare this to the last print(sims) in the previous section.

Note that the call method is not available for a list of simulations (only for a single simulation; see __call__). The return value would be too confusing. However, it's possible to retrieve information from multiple variables manually, e.g.,

In [ ]:
{"Final value of " + name: sims[name].FV() for name in ['C1.v', 'C2.v', 'L.v']}


Here we iterated over the variables. For a single simulation we could do this automatically:

In [ ]:
sims[0](['C1.v', 'C2.v', 'L.v']).FV()


where these values form the first "column" of the previous dictionary.

## Speed considerations¶

In the tutorial, we saw two ways to access variables: the get item method (square brackets) and the call method (parentheses). The get item method allows access to only one variable at a time but is a little faster:

In [4]:
sim = SimRes('ChuaCircuit.mat')

In [5]:
timeit sim['L.i'].values()

100000 loops, best of 3: 7.35 µs per loop

In [7]:
timeit sim(['L.i']).values()

100000 loops, best of 3: 10.1 µs per loop


Both of these approaches consist of two steps: accessing the variable (sim['L.i'] or sim('L.i')) and reading the values. The first step takes almost as much time as the second, but it only needs to be performed once. If we need to access a variable or a group of variables multiple times (to get their values, units, times, etc.), it's best to assign a variable to the entry or entries:

In [8]:
Li = sim['L.i']


With the first step out of the way, we can now retrieve information more quickly:

In [9]:
timeit Li.values()

100000 loops, best of 3: 6.87 µs per loop


The same holds for the call method. We'll access the voltages of all of the components in the ChuaCircuit:

In [10]:
voltages = sim.find('^[^.]*.v\$', re=True)


Running both steps at once

In [11]:
timeit sim(voltages).values()

10000 loops, best of 3: 50.3 µs per loop


takes longer than the second step alone:

In [12]:
v = sim(voltages)

In [13]:
timeit v.values()

10000 loops, best of 3: 44.6 µs per loop


As shown in the plot below, the simulation file loading time is fairly quick -- about one fifth of a second for a 3 MB file. The constants_only initialization option of SimRes saves about 15 to 25% of the load time and may be useful if it is only necessary to read parameters.

In [ ]:
d = DataFrame({'file size / B':[22273, 34990, 43027, 278277, 347461,
2332811, 3088223],
'load time / ms': [4.24, 2.97, 2.43, 13.6, 36.4,
68.6, 197]})
plt.plot(d['file size / B']/1e6, d['load time / ms'], 'o--')
plt.title("File loading time using ModelicaRes\n"
"Samsung ATIV Book 8, Intel Core i7-3635QM, Ubuntu 14.04")
plt.xlabel('File size / MB')
plt.ylabel('Loading time / ms')
plt.grid(True)


There appears to be about a 60% memory overhead associated with the data. A 278.3 kB file took approximately 4.5 GB of system memory when loaded 10,000 times:

In [ ]:
4.5e9/(1e4*278.3e3) - 1


## Contributing¶

Now you've seen the main features of ModelicaRes besides the exps module (tools to help set up and run simulation experiments). If there is a compelling use case or feature you'd like to see added, please consider developing it yourself and sharing it by a pull request to the master branch of the GitHub repository. The ModelicaRes source code is well documented and organized to allow expansion.