flowtracks 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowtracks/__init__.py +3 -0
- flowtracks/an_scene.py +188 -0
- flowtracks/analysis.py +146 -0
- flowtracks/format_docstrings.py +64 -0
- flowtracks/graphics.py +142 -0
- flowtracks/interpolation.py +799 -0
- flowtracks/io.py +911 -0
- flowtracks/pairs.py +82 -0
- flowtracks/particle.py +27 -0
- flowtracks/scene.py +499 -0
- flowtracks/sequence.py +356 -0
- flowtracks/smoothing.py +121 -0
- flowtracks/trajectory.py +314 -0
- flowtracks-1.1.0.data/data/flowtracks-examples/hdf5_scene_analysis.ipynb +464 -0
- flowtracks-1.1.0.data/data/flowtracks-examples/linking_trajectories.ipynb +223 -0
- flowtracks-1.1.0.data/data/flowtracks-examples/repeated_interpolation.ipynb +247 -0
- flowtracks-1.1.0.data/scripts/analyse_fhdf.py +34 -0
- flowtracks-1.1.0.dist-info/METADATA +110 -0
- flowtracks-1.1.0.dist-info/RECORD +22 -0
- flowtracks-1.1.0.dist-info/WHEEL +5 -0
- flowtracks-1.1.0.dist-info/licenses/LICENSE.txt +674 -0
- flowtracks-1.1.0.dist-info/top_level.txt +1 -0
flowtracks/__init__.py
ADDED
flowtracks/an_scene.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import tables, itertools as it, numpy as np
|
|
4
|
+
from .scene import read_dual_scene, gen_query_string
|
|
5
|
+
from .trajectory import Trajectory
|
|
6
|
+
|
|
7
|
+
class AnalysedScene(object):
|
|
8
|
+
"""
|
|
9
|
+
A class for accessing data and analyses of a scene analysed and saved in
|
|
10
|
+
the format used by flowtracks.analysis.analyse().
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, analysis_file):
|
|
14
|
+
"""
|
|
15
|
+
Initializes the objects according to config and data-source metadata
|
|
16
|
+
saved in the analysis file.
|
|
17
|
+
|
|
18
|
+
Arguments:
|
|
19
|
+
analysis_file - path to the HDF file containing analysis results.
|
|
20
|
+
"""
|
|
21
|
+
self._file = tables.open_file(analysis_file, "r")
|
|
22
|
+
self._table = self._file.get_node('/analysis')
|
|
23
|
+
|
|
24
|
+
config = self._table.attrs['config']
|
|
25
|
+
self._scene = read_dual_scene(config)
|
|
26
|
+
|
|
27
|
+
# Cache data on user-visible columsn:
|
|
28
|
+
filt = ('trajid', 'time')
|
|
29
|
+
self._keys = []
|
|
30
|
+
self._shapes = []
|
|
31
|
+
desc = self._table.coldescrs
|
|
32
|
+
for name in self._table.colnames:
|
|
33
|
+
if name in filt:
|
|
34
|
+
continue
|
|
35
|
+
self._keys.append(name)
|
|
36
|
+
shape = desc[name].shape
|
|
37
|
+
self._shapes.append(1 if len(shape) == 0 else shape[0])
|
|
38
|
+
|
|
39
|
+
def __del__(self):
|
|
40
|
+
self._file.close()
|
|
41
|
+
|
|
42
|
+
def keys(self):
|
|
43
|
+
"""
|
|
44
|
+
Return names that may be used to access data in any of the data sources
|
|
45
|
+
available, whether analyses or inertial particles.
|
|
46
|
+
"""
|
|
47
|
+
return list(self._scene.get_particles().keys()) + self._keys
|
|
48
|
+
|
|
49
|
+
def shapes(self):
|
|
50
|
+
"""
|
|
51
|
+
Return the number of components per item of each key in the order
|
|
52
|
+
returned by :meth:`keys`.
|
|
53
|
+
"""
|
|
54
|
+
return self._scene.get_particles().shapes() + self._shapes
|
|
55
|
+
|
|
56
|
+
def _iter_frame_arrays(self, cond=None):
|
|
57
|
+
"""
|
|
58
|
+
Private. Breaks the file down to its frames, and makes arrays of
|
|
59
|
+
them, iteratively. Also allows filtering the frames.
|
|
60
|
+
|
|
61
|
+
Arguments:
|
|
62
|
+
cond - an optional PyTables condition string to apply to each frame.
|
|
63
|
+
"""
|
|
64
|
+
query_string = '(time == t)'
|
|
65
|
+
if cond is not None:
|
|
66
|
+
query_string = '&'.join([query_string, cond])
|
|
67
|
+
|
|
68
|
+
for t in range(*self._scene.get_range()):
|
|
69
|
+
yield t, self._table.read_where(query_string)
|
|
70
|
+
|
|
71
|
+
def collect(self, keys, where=None):
|
|
72
|
+
"""
|
|
73
|
+
Get values of a given key, either some of them or the ones
|
|
74
|
+
corresponding to a selection given by 'where'
|
|
75
|
+
|
|
76
|
+
Arguments:
|
|
77
|
+
keys - a list of keys to take from the data
|
|
78
|
+
where - a dictionary of derived-results keys, with a tuple
|
|
79
|
+
(min,max,invert) as values. If ``invert`` is false, the search
|
|
80
|
+
range is between min and max. Otherwise it is anywhere except that.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
a list of arrays, in the order of ``keys``.
|
|
84
|
+
"""
|
|
85
|
+
# Divide the where condition into the trajectory conditions and
|
|
86
|
+
# analysis conditions.
|
|
87
|
+
part_cond = None
|
|
88
|
+
an_cond = None
|
|
89
|
+
|
|
90
|
+
pkeys = set(self._scene.get_particles().keys())
|
|
91
|
+
if where is not None:
|
|
92
|
+
pc_add = []
|
|
93
|
+
an_cond_add = []
|
|
94
|
+
|
|
95
|
+
for key, rng in where.items():
|
|
96
|
+
cond_string = gen_query_string(key, rng)
|
|
97
|
+
if key in pkeys:
|
|
98
|
+
pc_add.append(cond_string)
|
|
99
|
+
else:
|
|
100
|
+
an_cond_add.append(cond_string)
|
|
101
|
+
|
|
102
|
+
if len(pc_add) != 0:
|
|
103
|
+
part_cond = ' & '.join(pc_add)
|
|
104
|
+
if len(an_cond_add) != 0:
|
|
105
|
+
an_cond = ' & '.join(an_cond_add)
|
|
106
|
+
|
|
107
|
+
# Iterate over dual frame, each from the right source using the
|
|
108
|
+
# divided conditions.
|
|
109
|
+
res = dict((k, []) for k in keys)
|
|
110
|
+
|
|
111
|
+
dframe_it = it.izip(
|
|
112
|
+
self._scene.get_particles()._iter_frame_arrays(part_cond),
|
|
113
|
+
self._iter_frame_arrays(an_cond))
|
|
114
|
+
|
|
115
|
+
for tr_frm, an_frm in dframe_it:
|
|
116
|
+
# Cross reference matching rows.
|
|
117
|
+
tp, p_arr = tr_frm
|
|
118
|
+
ta, a_arr = an_frm
|
|
119
|
+
|
|
120
|
+
p_trids = p_arr['trajid']
|
|
121
|
+
a_trids = a_arr['trajid']
|
|
122
|
+
trajids = set(p_trids) & set(a_trids)
|
|
123
|
+
|
|
124
|
+
# select only those from the two frames:
|
|
125
|
+
in_p = np.array([True if tr in trajids else False \
|
|
126
|
+
for tr in p_trids])
|
|
127
|
+
in_a = np.array([True if tr in trajids else False \
|
|
128
|
+
for tr in a_trids])
|
|
129
|
+
|
|
130
|
+
if len(in_p) > 0:
|
|
131
|
+
p_arr = p_arr[in_p]
|
|
132
|
+
if len(a_arr) > 0:
|
|
133
|
+
a_arr = a_arr[in_a]
|
|
134
|
+
|
|
135
|
+
# For each dual frame, add the columns of specified keys to the list
|
|
136
|
+
# of that key's results
|
|
137
|
+
for k in keys:
|
|
138
|
+
if k in pkeys:
|
|
139
|
+
res[k].append(p_arr[k])
|
|
140
|
+
else:
|
|
141
|
+
res[k].append(a_arr[k])
|
|
142
|
+
|
|
143
|
+
# stack and return.
|
|
144
|
+
return [np.concatenate(res[k], axis=0) for k in keys]
|
|
145
|
+
|
|
146
|
+
def trajectory_by_id(self, trid):
|
|
147
|
+
"""
|
|
148
|
+
Retrieves an inertial trajectory with the respective analysis.
|
|
149
|
+
See ``iter_trajectories`` for the full documentation.
|
|
150
|
+
|
|
151
|
+
Arguments:
|
|
152
|
+
trid - trajectory ID to fetch.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
a Trajectory object with analysis keys added.
|
|
156
|
+
"""
|
|
157
|
+
traj = self._scene.get_particles().trajectory_by_id(trid)
|
|
158
|
+
|
|
159
|
+
# Trim last point of trajectory to match analysis:
|
|
160
|
+
kwds = dict((k, v[:-1]) for k, v in traj.as_dict().items())
|
|
161
|
+
kwds['trajid'] = trid
|
|
162
|
+
|
|
163
|
+
# Fetch by foreign key from analysis:
|
|
164
|
+
query_string = '(trajid == trid)'
|
|
165
|
+
arr = self._table.read_where(query_string)
|
|
166
|
+
|
|
167
|
+
# Update into the Trajectory object:
|
|
168
|
+
for field in arr.dtype.fields:
|
|
169
|
+
if field not in ['trajid', 'time']:
|
|
170
|
+
kwds[field] = arr[field]
|
|
171
|
+
|
|
172
|
+
return Trajectory(**kwds)
|
|
173
|
+
|
|
174
|
+
def iter_trajectories(self):
|
|
175
|
+
"""
|
|
176
|
+
Iterator over inertial trajectories. Since the analysis is structured
|
|
177
|
+
around the inertial particles of the internal DualScene, it is possible
|
|
178
|
+
to iterate those trajectories, adding the corresponding fields of
|
|
179
|
+
analysis to the same object. Generates a Trajectory object for each
|
|
180
|
+
inertial particle trajectory in the particles file (in no particular
|
|
181
|
+
order, but the same order every time on the same PyTables version) and
|
|
182
|
+
yields it.
|
|
183
|
+
|
|
184
|
+
Note: since analysis works on segments, it truncates the last point of
|
|
185
|
+
each trajectory.
|
|
186
|
+
"""
|
|
187
|
+
for trid in self._scene.get_particles().trajectory_ids():
|
|
188
|
+
yield self.trajectory_by_id(trid)
|
flowtracks/analysis.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Created on Mon Aug 11 15:14:21 2014
|
|
3
|
+
"""
|
|
4
|
+
Infrastructure for running a frame-by-frame analysis on a DualScene object.
|
|
5
|
+
The main point of interest here is :func:`analysis`, which performs a segment
|
|
6
|
+
iteration over a :class:`~flowtracks.scene.DualScene` and applies to each
|
|
7
|
+
a user-selected list of analyzers. Analysers are instances of a
|
|
8
|
+
:class:`GeneralAnalyser` subclass which implements the necessary methods,
|
|
9
|
+
as described in the base class documentation.
|
|
10
|
+
|
|
11
|
+
There is one base class supplied here, :class:`FluidVelocitiesAnalyser`,
|
|
12
|
+
which ties in the :mod:`flowtracks.interpolation` module for analysing the
|
|
13
|
+
fluid velocity around a particle from its surrounding tracers.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import numpy as np, tables
|
|
17
|
+
|
|
18
|
+
def companion_indices(trids, companions):
|
|
19
|
+
"""
|
|
20
|
+
Return an array giving for each companion its respective index in the
|
|
21
|
+
trajectory ID array, or a negative number if not found.
|
|
22
|
+
"""
|
|
23
|
+
comp = np.full_like(companions, -1)
|
|
24
|
+
idx = np.nonzero(trids[:,None] == companions)
|
|
25
|
+
comp[idx[1]] = idx[0]
|
|
26
|
+
return comp
|
|
27
|
+
|
|
28
|
+
class GeneralAnalyser(object):
|
|
29
|
+
"""
|
|
30
|
+
This is the parent class for all analysers to be used by :func:`analysis`.
|
|
31
|
+
It does not do anything but define and document the methods that must be
|
|
32
|
+
implenmented by the child class (in other words, this class is abstract).
|
|
33
|
+
Attempting to use its methods will result in a ``NotImplementedError``.
|
|
34
|
+
"""
|
|
35
|
+
def descr(self):
|
|
36
|
+
"""
|
|
37
|
+
Need to return a list of tuples, each of the form
|
|
38
|
+
(name, data type, row length), e.g. ('trajid', int, 1)
|
|
39
|
+
"""
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
def analyse(self, frame, next_frame):
|
|
43
|
+
"""
|
|
44
|
+
Arguments:
|
|
45
|
+
frame, next_frame - the Frame object for the currently-analysed frame
|
|
46
|
+
and the one after it, respectively.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
a list of arrays, each of shape (f,d) where f is the number of
|
|
50
|
+
particles in the current frame, and d is the row length of the
|
|
51
|
+
corresponding item returned by self.descr(). Each array's dtype also
|
|
52
|
+
corresponds to the dtype given to it by self.descr().
|
|
53
|
+
"""
|
|
54
|
+
raise NotImplementedError
|
|
55
|
+
|
|
56
|
+
class FluidVelocitiesAnalyser(GeneralAnalyser):
|
|
57
|
+
"""
|
|
58
|
+
Finds, for each particle in the ``particles`` set of a frame, the
|
|
59
|
+
so-called *undisturbed* fluid velocity at the particle's position, by
|
|
60
|
+
interpolating from nearby particles in the ``tracers`` set.
|
|
61
|
+
"""
|
|
62
|
+
def __init__(self, interp):
|
|
63
|
+
"""
|
|
64
|
+
Arguments:
|
|
65
|
+
interp - the Interpolant object to use for finding velocities.
|
|
66
|
+
"""
|
|
67
|
+
self._interp = interp
|
|
68
|
+
|
|
69
|
+
def descr(self):
|
|
70
|
+
"""
|
|
71
|
+
Return a list of two tuples, each of the form
|
|
72
|
+
(name, data type, row length), describing the arrays returned by
|
|
73
|
+
analyse() for fluid velocity and relative velocity.
|
|
74
|
+
"""
|
|
75
|
+
return [('fluid_vel', float, 3), ('rel_vel', float, 3)]
|
|
76
|
+
|
|
77
|
+
def analyse(self, frame, next_frame):
|
|
78
|
+
"""
|
|
79
|
+
Arguments:
|
|
80
|
+
frame, next_frame - the Frame object for the currently-analysed frame
|
|
81
|
+
and the one after it, respectively.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
a list of two arrays, each of shape (f,3) where f is the number of
|
|
85
|
+
particles in the current frame. 1st array - fluid velocity. 2nd array
|
|
86
|
+
- relative velocity.
|
|
87
|
+
"""
|
|
88
|
+
if frame.particles.has_property('companion'):
|
|
89
|
+
comp = companion_indices(
|
|
90
|
+
frame.tracers.trajid(), frame.particles.companion())
|
|
91
|
+
else:
|
|
92
|
+
comp = None
|
|
93
|
+
self._interp.set_scene(frame.tracers.pos(), frame.particles.pos(),
|
|
94
|
+
frame.tracers.velocity(), comp)
|
|
95
|
+
vel_interp = self._interp.interpolate()
|
|
96
|
+
rel_vel = frame.particles.velocity() - vel_interp
|
|
97
|
+
|
|
98
|
+
return [vel_interp, rel_vel]
|
|
99
|
+
|
|
100
|
+
def analysis(scene, analysis_file, conf_file, analysers, frame_range=-1):
|
|
101
|
+
"""
|
|
102
|
+
Generate the analysis table for a given scene with separate data for
|
|
103
|
+
inertial particles and tracers.
|
|
104
|
+
|
|
105
|
+
Arguments:
|
|
106
|
+
scene - a DualScene object representing an experiment with coordinated
|
|
107
|
+
particles and tracers data streams.
|
|
108
|
+
analysis_file - path to the file where analysis should be saved. If the
|
|
109
|
+
file exists, it will be cloberred.
|
|
110
|
+
conf_file - name of config file used for creating the analysis.
|
|
111
|
+
analysers - a list of GeneralAnalyser subclasses that do the actual
|
|
112
|
+
analysis work and know all that is needed about output shape.
|
|
113
|
+
frame_range - if -1 no adjustment is necessary, otherwise see
|
|
114
|
+
:meth:`DualScene.iter_segments() <flowtracks.scene.DualScene.iter_segments>`
|
|
115
|
+
"""
|
|
116
|
+
# Structure the output file:
|
|
117
|
+
descr = [('trajid', int, 1), ('time', int, 1)]
|
|
118
|
+
for analyser in analysers:
|
|
119
|
+
descr.extend(analyser.descr())
|
|
120
|
+
descr = np.dtype(descr)
|
|
121
|
+
|
|
122
|
+
outfile = tables.open_file(analysis_file, "w", title="Analysis results.")
|
|
123
|
+
table = outfile.create_table('/', 'analysis', descr)
|
|
124
|
+
table.attrs['config'] = conf_file
|
|
125
|
+
table.attrs['trajects'] = scene.get_particles_path()
|
|
126
|
+
|
|
127
|
+
for frame, next_frame in scene.iter_segments(frame_range):
|
|
128
|
+
length = len(frame.particles)
|
|
129
|
+
arr = np.empty(length, dtype=descr)
|
|
130
|
+
arr['trajid'] = frame.particles.trajid()
|
|
131
|
+
arr['time'] = frame.particles.time()
|
|
132
|
+
|
|
133
|
+
for analyser in analysers:
|
|
134
|
+
analysis = analyser.analyse(frame, next_frame)
|
|
135
|
+
this_descr = analyser.descr()
|
|
136
|
+
|
|
137
|
+
for res, desc in zip(analysis, this_descr):
|
|
138
|
+
arr[desc[0]] = res
|
|
139
|
+
|
|
140
|
+
table.append(arr)
|
|
141
|
+
|
|
142
|
+
# Wrap up and close.
|
|
143
|
+
table.cols.trajid.create_index()
|
|
144
|
+
table.cols.time.create_index()
|
|
145
|
+
outfile.flush()
|
|
146
|
+
outfile.close()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# A Sphinx extension that, in collaboration with the autodoc extension,
|
|
2
|
+
# parses the docstring format used in this project.
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
# Section processing regexes:
|
|
7
|
+
end_section = re.compile(r'^\s*$')
|
|
8
|
+
section_title = re.compile(r'^\s*(\w+):')
|
|
9
|
+
def_line = re.compile(r'(\w+(?:,\s*\w+)*)\s+-\s+(.+)')
|
|
10
|
+
|
|
11
|
+
def setup(app):
|
|
12
|
+
app.connect('autodoc-process-docstring', parse_docstring)
|
|
13
|
+
|
|
14
|
+
def parse_docstring(app, what, name, obj, options, lines):
|
|
15
|
+
"""
|
|
16
|
+
Divides the docstring into sections, separated by an empty line in the
|
|
17
|
+
docstring. If a section starts with a line like "$some_title:", then it is
|
|
18
|
+
turned into a titled section with $some_title as its title.
|
|
19
|
+
|
|
20
|
+
A line starting with a python identifier, then a dash, then a definition,
|
|
21
|
+
is turned into a definition-list item.
|
|
22
|
+
"""
|
|
23
|
+
out_lines = []
|
|
24
|
+
place = 0
|
|
25
|
+
section = []
|
|
26
|
+
bullet = False
|
|
27
|
+
for place in range(len(lines)):
|
|
28
|
+
line = lines[place]
|
|
29
|
+
|
|
30
|
+
# End section: flush into out_lines.
|
|
31
|
+
m = end_section.match(line)
|
|
32
|
+
if m is not None:
|
|
33
|
+
out_lines.extend(section + [line])
|
|
34
|
+
section = []
|
|
35
|
+
bullet = False
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
if len(section) == 0:
|
|
39
|
+
# Start of a title-section:
|
|
40
|
+
m = section_title.match(line)
|
|
41
|
+
if m is not None:
|
|
42
|
+
section.extend(['*' + m.group(1) + '*', ''])
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
m = def_line.match(line)
|
|
46
|
+
if m is not None:
|
|
47
|
+
out_lines.extend(section + [''])
|
|
48
|
+
section = []
|
|
49
|
+
bullet = True
|
|
50
|
+
section.extend(['', '* ``' + m.group(1) + '``: ' + m.group(2)])
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
line = line.strip()
|
|
54
|
+
if bullet:
|
|
55
|
+
line = ' ' + line
|
|
56
|
+
section.append(line)
|
|
57
|
+
|
|
58
|
+
# Flush last section:
|
|
59
|
+
out_lines.extend(section)
|
|
60
|
+
section=[]
|
|
61
|
+
|
|
62
|
+
# In-place modification of `lines` is needed:
|
|
63
|
+
lines[:] = out_lines
|
|
64
|
+
|
flowtracks/graphics.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Created on Sun Sep 22 16:11:34 2013
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Various specialized graphing routines. The Probability Density Function
|
|
6
|
+
graphing is best accessed by calling :func:`pdf_graph` on the raw data, but
|
|
7
|
+
you can generate the PDF from the data separately (e.g. using
|
|
8
|
+
:func:`pdf_bins`) and calling :func:`generalized_histogram_disp` on the
|
|
9
|
+
result.
|
|
10
|
+
|
|
11
|
+
The other facility here is a function to plot a time-dependent 3D vector as
|
|
12
|
+
3 component subplots, which is another customary presentation in fluid
|
|
13
|
+
dynamics circles. See :func:`plot_vectors`.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import numpy as np, matplotlib.pyplot as pl
|
|
17
|
+
|
|
18
|
+
def pdf_bins(data, num_bins, log_bins=False):
|
|
19
|
+
"""
|
|
20
|
+
Generate a PDF of the given data possibly with logarithmic bins, ready for
|
|
21
|
+
using in a histogram plot.
|
|
22
|
+
|
|
23
|
+
Arguments:
|
|
24
|
+
data - the samples to histogram.
|
|
25
|
+
bins - the number of bins in the histogram.
|
|
26
|
+
log_bins - if True, the bin edges are equally spaced on the log scale,
|
|
27
|
+
otherwise they are linearly spaced (a normal histogram). If True,
|
|
28
|
+
``data`` should not contain zeros.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
hist - num_bins-lenght array of density values for each bin.
|
|
32
|
+
bin_edges - array of size num_bins + 1 with the edges of the bins including
|
|
33
|
+
the ending limit of the bins.
|
|
34
|
+
"""
|
|
35
|
+
if log_bins:
|
|
36
|
+
data = data[data > 0]
|
|
37
|
+
minv = np.min(data)
|
|
38
|
+
bins = np.logspace(np.log10(minv), np.log10(data.max()), num_bins + 1)
|
|
39
|
+
else:
|
|
40
|
+
bins = num_bins
|
|
41
|
+
|
|
42
|
+
hist, bin_edges = np.histogram(data, bins=bins, density=True)
|
|
43
|
+
return hist, bin_edges
|
|
44
|
+
|
|
45
|
+
def generalized_histogram_disp(hist, bin_edges, log_bins=False,
|
|
46
|
+
log_density=False, marker='o'):
|
|
47
|
+
"""
|
|
48
|
+
Draws a given histogram according to the visual custom of the fluid
|
|
49
|
+
dynamics community.
|
|
50
|
+
|
|
51
|
+
Arguments:
|
|
52
|
+
hist - an array containing the number of values (or density) for each bin.
|
|
53
|
+
bin_edges - the start value of each bin, same length as ``hist``.
|
|
54
|
+
log_bins - indicates that the bin edges are log-spaced.
|
|
55
|
+
log_densify - Show the log of the probability density value. May cause
|
|
56
|
+
problems if ``log_bins`` is True.
|
|
57
|
+
marker - marker style for matplotlib.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
the list of lines drawn, Matplotlib objects.
|
|
61
|
+
"""
|
|
62
|
+
if log_bins:
|
|
63
|
+
plt = pl.loglog if log_density else pl.semilogx
|
|
64
|
+
else:
|
|
65
|
+
plt = pl.semilogy if log_density else pl.plot
|
|
66
|
+
|
|
67
|
+
lines = plt(bin_edges, hist, marker)
|
|
68
|
+
pl.ylabel("Probability density [-]")
|
|
69
|
+
|
|
70
|
+
return lines
|
|
71
|
+
|
|
72
|
+
def pdf_graph(data, num_bins, log=False, log_density=False, marker='o'):
|
|
73
|
+
"""
|
|
74
|
+
Draw a PDF of the given data, according to the visual custom of
|
|
75
|
+
the fluid dynamics community, and possibly with logarithmic bins.
|
|
76
|
+
|
|
77
|
+
Arguments:
|
|
78
|
+
data - the samples to histogram.
|
|
79
|
+
bins - the number of bins in the histogram.
|
|
80
|
+
log - if True, the bin edges are equally spaced on the log scale, otherwise
|
|
81
|
+
they are linearly spaced (a normal histogram). If True, ``data`` should
|
|
82
|
+
not contain zeros.
|
|
83
|
+
log_density - Show the log of the probability density value. Only if log
|
|
84
|
+
is False.
|
|
85
|
+
marker - override the circle marker with any string acceptable to
|
|
86
|
+
matplotlib.
|
|
87
|
+
"""
|
|
88
|
+
hist, bin_edges = pdf_bins(data, num_bins, log)
|
|
89
|
+
generalized_histogram_disp(hist, bin_edges[:-1], log, log_density,
|
|
90
|
+
marker='-' + marker)
|
|
91
|
+
|
|
92
|
+
def plot_vectors(vecs, indep, xlabel, fig=None, marker='-',
|
|
93
|
+
ytick_dens=None, yticks_format=None, unit_str="", common_scale=None,
|
|
94
|
+
arrows=None, arrow_color=None):
|
|
95
|
+
"""
|
|
96
|
+
Plot 3D vectors as 3 subplots sharing the same independent axis.
|
|
97
|
+
|
|
98
|
+
Arguments:
|
|
99
|
+
vecs - an (n,3) array, with n vectors to plot against the independent
|
|
100
|
+
variable.
|
|
101
|
+
indep - the corresponding n values of the independent variable.
|
|
102
|
+
xlabel - label for the independent axis.
|
|
103
|
+
fig - an optional figure object to use. If None, one will be created.
|
|
104
|
+
ytick_dens - if not None, place this many yticks on each subplot, instead
|
|
105
|
+
of the automatic tick marks.
|
|
106
|
+
yticks_format - a pyplot formatter object.
|
|
107
|
+
unit_str - a string to add to the Y labels representing the vector's units.
|
|
108
|
+
arrows - an (n,3) array of values to represent as vertical arrows attached
|
|
109
|
+
to each trajectory point.
|
|
110
|
+
arrow_color - a matplotlib color spec for the arrow bodies.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
fig - the figure object used for plotting.
|
|
114
|
+
"""
|
|
115
|
+
fig = pl.figure(None if fig is None else fig.number)
|
|
116
|
+
u = np.zeros(vecs.shape[0])
|
|
117
|
+
|
|
118
|
+
labels = ("X " + unit_str, "Y" + unit_str, "Z" + unit_str)
|
|
119
|
+
for subplt in range(3):
|
|
120
|
+
pl.subplot(3,1,subplt + 1)
|
|
121
|
+
pl.plot(indep, vecs[:,subplt], marker)
|
|
122
|
+
pl.gca().get_xaxis().set_visible(False)
|
|
123
|
+
pl.grid()
|
|
124
|
+
pl.ylabel(labels[subplt])
|
|
125
|
+
|
|
126
|
+
if yticks_format is not None:
|
|
127
|
+
pl.gca().get_yaxis().set_major_formatter(yticks_format)
|
|
128
|
+
|
|
129
|
+
if common_scale is not None:
|
|
130
|
+
pl.ylim(np.r_[-common_scale, common_scale] + vecs[:,subplt].mean())
|
|
131
|
+
if ytick_dens is not None:
|
|
132
|
+
loc, _ = pl.yticks()
|
|
133
|
+
pl.yticks(np.linspace(vecs[:,subplt].min(), vecs[:,subplt].max(),
|
|
134
|
+
ytick_dens))
|
|
135
|
+
|
|
136
|
+
if arrows is not None:
|
|
137
|
+
pl.quiver(indep, vecs[:,subplt], u, arrows[:,subplt],
|
|
138
|
+
scale=30, width=1e-3, color=arrow_color)
|
|
139
|
+
|
|
140
|
+
pl.gca().get_xaxis().set_visible(True)
|
|
141
|
+
pl.xlabel(xlabel)
|
|
142
|
+
return fig
|