This IPython notebook demonstrates some of the advanced features and use cases of ModelicaRes.
First, we'll load the ModelicaRes classes we'll need:
from modelicares import SimRes, SimResList
and some standard modules and settings for this IPython notebook:
import numpy as np
import matplotlib.pyplot as plt
from pandas import DataFrame
%matplotlib inline
%precision 3
u'%.3f'
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:
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.
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:
sims = SimResList('*.mat', '*/*/*.mat')
Let's see which simulations are in the list:
print(sims)
The directory contained some linearization results, but they were excluded automatically.
Now let's see which variables are available:
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):
has_inductor = lambda sim: 'L.L' in sim
Let's see if each simulation passes the test:
[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:
sims.unique_names['L.L']
Anyway, let's go ahead and filter our simulation list to those that have the inductor:
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:
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.,
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.
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.,
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:
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:
'L.L' in sims
or if a simulation is in the list of simulations:
sims[0] in sims
It is possible to use slices to extract a SimResList with selected simulations:
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.,
{"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:
sims[0](['C1.v', 'C2.v', 'L.v']).FV()
where these values form the first "column" of the previous dictionary.
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:
sim = SimRes('ChuaCircuit.mat')
timeit sim['L.i'].values()
100000 loops, best of 3: 7.35 µs per loop
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:
Li = sim['L.i']
With the first step out of the way, we can now retrieve information more quickly:
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:
voltages = sim.find('^[^.]*.v$', re=True)
Running both steps at once
timeit sim(voltages).values()
10000 loops, best of 3: 50.3 µs per loop
takes longer than the second step alone:
v = sim(voltages)
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.
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:
4.5e9/(1e4*278.3e3) - 1
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.