piegy 2.1.0__cp38-cp38-win32.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.
@@ -0,0 +1,238 @@
1
+ '''
2
+ Helper functions for making figures.
3
+
4
+ Functions:
5
+ - heatmap: Make a heatmap based on input data. Sets title, text ... as well
6
+ - bar: Make a barplot. Sets title, text ... as well.
7
+ - scatter: Make a scatter plot. Sets title, text ... as well.
8
+ - gen_title: Generates a title when the plot is about an interval of time.
9
+ - gen_text: Generates a text about standard deviation info.
10
+ - scale_interval: scale interval if mod's data was already reduced.
11
+ - ave_interval: Calculates average value of data over a time interval.
12
+ - ave_interval_1D: Return in a 1D format.
13
+ - config_mpl: Configure Matplotlib parameters in a nice format
14
+ '''
15
+
16
+
17
+ import matplotlib.pyplot as plt
18
+ import numpy as np
19
+ import seaborn as sns
20
+
21
+ # move ax a bit left if add text
22
+ # default value is [0.125, 0.11, 0.9, 0.88]
23
+
24
+
25
+ def heatmap(data, ax = None, cmap = "Greens", annot = False, fmt = '.3g', title = None, text = None):
26
+ '''
27
+ Helper function for making heatmaps.
28
+
29
+ Inputs:
30
+ data: 1D data for which you want to make a heatmap.
31
+ ax: matplotlib ax to plot on.
32
+ cmap: Color of heatmap. Uses matplotlib color maps
33
+ annot: Whether to show numbers of every block.
34
+ fmt: Number format for annotations. How many digits you want to keep.
35
+ title: The title you want to add. None means no title.
36
+ text: Adds some text in a text block at the top-right corner.
37
+
38
+ Returns:
39
+ fig: Seaborn heatmap.
40
+ '''
41
+
42
+ if ax == None:
43
+ _, ax = plt.subplots()
44
+
45
+ if text != None:
46
+ ax.text(0.8, 1.025, text, size = 10, linespacing = 1.5, transform = ax.transAxes)
47
+
48
+ ax = sns.heatmap(data, ax = ax, cmap = cmap, annot = annot, fmt = fmt)
49
+ ax.set_title(title, x = 0.5, y = 1)
50
+
51
+ return ax
52
+
53
+
54
+
55
+ def bar(data, ax = None, color = "green", xlabel = None, ylabel = None, title = None, text = None):
56
+ '''
57
+ Helper Function for making barplots.
58
+
59
+ Inputs:
60
+ data: 2D data to make barplot.
61
+ ax: matplotlib ax to plot on.
62
+ color: Uses Matplotlib colors.
63
+ xlabel, y_label:
64
+ Label for axes.
65
+ title: Title for the barplot.
66
+ text: Adds some text in a text block at the top-right corner.
67
+
68
+ Returns:
69
+ fig: A Matplotlib barplot.
70
+ '''
71
+
72
+ N = np.array(data).size
73
+ xaxis = np.array([i for i in range(N)])
74
+
75
+ if ax == None:
76
+ if N > 60:
77
+ # make figure larger if has more data points
78
+ _, ax = plt.subplots(figsize = (min(N * 0.107, 6.4), 4.8))
79
+ else:
80
+ _, ax = plt.subplots()
81
+
82
+ if text != None:
83
+ ax.text(0.7, 1.025, text, size = 10, linespacing = 1.5, transform = ax.transAxes)
84
+
85
+ ax.bar(x = xaxis, height = data, color = color)
86
+ ax.set_xlabel(xlabel)
87
+ ax.set_ylabel(ylabel)
88
+ ax.set_title(title, x = 0.5, y = 1)
89
+
90
+ return ax
91
+
92
+
93
+
94
+ def scatter(X, Y, ax = None, color = "orange", alpha = 0.25, xlabel = "x", ylabel = "y", title = None):
95
+ '''
96
+ Helper function for makeing scatter plots.
97
+
98
+ Inputs:
99
+ X: x-coordinates of points.
100
+ Y: y-coordinates of points.
101
+ ax: matplotlib ax to plot on.
102
+ Note color is Matplotlib colors.
103
+
104
+ Returns:
105
+ fig: A Matplotlib scatter plot.
106
+ '''
107
+
108
+ if ax == None:
109
+ _, ax = plt.subplots(figsize = (7.2, 5.4))
110
+ ax.scatter(X, Y, color = color, alpha = alpha)
111
+
112
+ ax.set_xlabel(xlabel)
113
+ ax.set_ylabel(ylabel)
114
+ ax.set_title(title)
115
+
116
+ return ax
117
+
118
+
119
+
120
+ def gen_title(title, start, end):
121
+ '''
122
+ Generate a title for plot when it's about an interval of time.
123
+ '''
124
+ title += ", " + str(round(start * 100, 1)) + "~" + str(round(end * 100, 1)) + "%"
125
+ return title
126
+
127
+
128
+
129
+ def gen_text(ave, std):
130
+ '''
131
+ Generate text about standard deviation info.
132
+ '''
133
+ text = "ave=" + str(round(ave, 2)) + ", std=" + str(round(std, 2))
134
+ return text
135
+
136
+
137
+
138
+ def ave_interval(data, start_index, end_index):
139
+ '''
140
+ Calculate average value of data over an interval. Return a 2D np.array
141
+ Assume data is 3D with shape N x M x K, then takes average on the 3rd axis.
142
+
143
+ Input:
144
+ data: 3D np.array or list. Will take average on the 3rd axis.
145
+ start_index, end_index:
146
+ over what interval to take average.
147
+
148
+ Returns:
149
+ data_ave: 2D np.array with shape N x M, contains average value of data.
150
+ '''
151
+
152
+ N = len(data)
153
+ M = len(data[0])
154
+
155
+ # plot a particular record
156
+ if start_index == end_index:
157
+ start_index = end_index - 1
158
+
159
+ data_ave = np.zeros((N, M))
160
+
161
+ for i in range(N):
162
+ for j in range(M):
163
+ for k in range(start_index, end_index):
164
+ data_ave[i][j] += data[i][j][k]
165
+ data_ave[i][j] /= (end_index - start_index)
166
+
167
+ return data_ave
168
+
169
+
170
+
171
+ def ave_interval_1D(data, start_index, end_index):
172
+ '''
173
+ Calculate average value of data over an interval. Return a 1D np.array.
174
+ Assume data is 3D and has shape (1 x M x K) or (N x 1 x K). Then implicitly 'compress' that 1 and takes average on the 3rd axis.
175
+
176
+ Input:
177
+ data: 3D np.array or list. One of its dimensions must have size 1. Will take average on the 3rd axis.
178
+ start_index, end_index:
179
+ over what interval to take average.
180
+
181
+ Returns:
182
+ data_ave: 1D np.array with len N * M, contains average value of data.
183
+ '''
184
+
185
+ N = len(data)
186
+ M = len(data[0])
187
+
188
+ if start_index == end_index:
189
+ start_index = end_index - 1
190
+
191
+ data_ave = np.zeros(N * M)
192
+
193
+ for i in range(N):
194
+ for j in range(M):
195
+ for k in range(start_index, end_index):
196
+ data_ave[i * M + j] += data[i][j][k]
197
+ data_ave[i * M + j] /= (end_index - start_index)
198
+
199
+ return data_ave
200
+
201
+
202
+
203
+ def scale_interval(interval, compress_itv):
204
+ # scale interval if mod's data was already reduced.
205
+ if compress_itv < 1:
206
+ raise ValueError('figures.scale_interval has compress_itv < 1:', compress_itv)
207
+
208
+ interval = int(interval / compress_itv)
209
+ if interval == 0:
210
+ print('Warning: data already smoothed by an interval: mod.compress_itv =', compress_itv, 'which is coarser than your', interval)
211
+ interval = 1
212
+
213
+ return interval
214
+
215
+
216
+
217
+ def config_mpl(mpl):
218
+ '''
219
+ Configure Matplotlib figures
220
+ '''
221
+ mpl.rcParams['savefig.dpi'] = 300
222
+
223
+ mpl.rcParams['font.family'] = 'serif'
224
+ mpl.rcParams['font.serif'] = plt.rcParams['font.serif']
225
+ mpl.rcParams['lines.linewidth'] = 1.75
226
+ mpl.rcParams['font.size'] = 11
227
+ mpl.rcParams['axes.labelsize'] = 12
228
+ mpl.rcParams['xtick.major.size'] = 10
229
+ mpl.rcParams['ytick.major.size'] = 9
230
+ mpl.rcParams['xtick.minor.size'] = 4
231
+ mpl.rcParams['ytick.minor.size'] = 4
232
+
233
+ mpl.rcParams['xtick.major.width'] = 1.5
234
+ mpl.rcParams['ytick.major.width'] = 1.5
235
+ mpl.rcParams['xtick.minor.width'] = 1.5
236
+ mpl.rcParams['ytick.minor.width'] = 1.5
237
+
238
+
@@ -0,0 +1,29 @@
1
+ '''
2
+ File-processing tools.
3
+
4
+ Functions:
5
+ - del_dirs: Delete everything in a directory, as well as the directory itself.
6
+ '''
7
+
8
+ import os
9
+
10
+
11
+ def del_dirs(dirs):
12
+ # Delete everything in a directory.
13
+
14
+ subdirs_list = []
15
+
16
+ for subdirs, dirs_, files in os.walk(dirs):
17
+ if subdirs not in subdirs_list:
18
+ subdirs_list.append(subdirs)
19
+
20
+ for file in files:
21
+ path = os.path.join(subdirs, file)
22
+ if os.path.isfile(path):
23
+ os.remove(path)
24
+
25
+ len_s = len(subdirs_list)
26
+
27
+ for i in range(len_s):
28
+ os.rmdir(subdirs_list[len_s - i - 1])
29
+
piegy/videos.py ADDED
@@ -0,0 +1,303 @@
1
+ '''
2
+ Make mp4 videos for simulation results.
3
+
4
+ Videos are made by:
5
+ make every frame by figures.py functions, then put frames together into a video.
6
+
7
+ Public Function:
8
+ - make_video: make video based simulation results.
9
+
10
+ Private Functions
11
+ - get_max_lim: Get the max lim (interval) over many lims, and then expand it a bit for better accommodation.
12
+ Essentially takes union of those intervals.
13
+ - video_lim: Find a large enough xlim and ylim for video.
14
+ - make_mp4: Put frames together into a mp4.
15
+ others not documented here.
16
+
17
+ '''
18
+
19
+
20
+ from . import figures
21
+ from .tools import file_tools as file_t
22
+ from .tools import figure_tools as figure_t
23
+
24
+ import matplotlib as mpl
25
+ import matplotlib.pyplot as plt
26
+ import numpy as np
27
+ import os
28
+ from moviepy import ImageSequenceClip
29
+
30
+
31
+ # a list of supported figures
32
+ SUPPORTED_FIGURES = ['UV_heatmap', 'pi_heatmap', 'UV_bar', 'pi_bar', 'UV_hist', 'pi_hist', 'UV_pi']
33
+
34
+
35
+ # map function name to functios in figures.py
36
+ # functions not in this dictionary is not supported for videos.
37
+ FUNC_DICT = {'UV_heatmap': figures.UV_heatmap, 'UV_bar': figures.UV_bar, 'UV_hist': figures.UV_hist,
38
+ 'pi_heatmap': figures.pi_heatmap, 'pi_bar': figures.pi_bar, 'pi_hist': figures.pi_hist, 'UV_pi': figures.UV_pi}
39
+
40
+
41
+ # Map some color maps to regular colors, used to change colors when an invalid color name is given
42
+ SNS_PLT_COLOR_DICT = {'Greens': 'green', 'Purples': 'purple', 'BuPu': 'violet', 'YlGn': 'yellowgreen'}
43
+ # Map regular colors to color maps
44
+ PLT_SNS_COLOR_DICT = {'green': 'Greens', 'purple': 'Purples', 'violet': 'BuPu', 'yellowgreen': 'YlGn'}
45
+
46
+
47
+
48
+
49
+ def convert_color(func_name, U_color, V_color):
50
+ '''
51
+ Converts some invalid colors.
52
+ If making heatmap videos but gave single colors, map to color maps.
53
+ If making barplot or histogram videos but gave single colors, map to Matplotlib
54
+ '''
55
+
56
+ if 'heatmap' in func_name:
57
+ # if making heatmaps but give regular colors
58
+ if U_color in PLT_SNS_COLOR_DICT.keys():
59
+ print('Making heatmaps, changed \'' + U_color + '\' to \'' + PLT_SNS_COLOR_DICT[U_color] + '\'')
60
+ U_color = PLT_SNS_COLOR_DICT[U_color]
61
+ if V_color in PLT_SNS_COLOR_DICT.keys():
62
+ print('Making heatmaps, changed \'' + V_color + '\' to \'' + PLT_SNS_COLOR_DICT[V_color] + '\'')
63
+ V_color = PLT_SNS_COLOR_DICT[V_color]
64
+
65
+ return U_color, V_color
66
+
67
+ elif 'heatmap' not in func_name:
68
+ # if making barplots or histogram
69
+ if U_color in SNS_PLT_COLOR_DICT.keys():
70
+ print('Not making heatmaps, changed \'' + U_color + '\' to \'' + SNS_PLT_COLOR_DICT[U_color] + '\'')
71
+ U_color = SNS_PLT_COLOR_DICT[U_color]
72
+ if V_color in SNS_PLT_COLOR_DICT.keys():
73
+ print('Not making heatmaps, changed \'' + V_color + '\' to \'' + SNS_PLT_COLOR_DICT[V_color] + '\'')
74
+ V_color = SNS_PLT_COLOR_DICT[V_color]
75
+
76
+ return U_color, V_color
77
+
78
+
79
+
80
+ def get_max_lim(lims):
81
+ '''
82
+ Get the max lim over many lims, i.e., the lowest lower bound and highest upper bound.
83
+ And then expand it a bit for better accommodation.
84
+
85
+ Input:
86
+ lim: list or np.array, has form [lim1, lim2, ...]
87
+
88
+ Returns:
89
+ A max lim which contains all lims.
90
+ '''
91
+
92
+ lims = np.array(lims)
93
+
94
+ lim_min = np.min(lims[:, 0]) # min of min
95
+ lim_max = np.max(lims[:, 1]) # max of max
96
+ r = lim_max - lim_min
97
+
98
+ if lim_min != 0:
99
+ # negative values are reached
100
+ # extend both upper bound and lower bound
101
+ return [lim_min - r * 0.05, lim_max + r * 0.05]
102
+ else:
103
+ # only extend upper bound
104
+ return [0, lim_max + r * 0.05]
105
+
106
+
107
+
108
+
109
+ def frame_lim(mod, func, frames):
110
+ '''
111
+ Find a large enough xlim and ylim for frames, if not heatmaps.
112
+
113
+ Inputs:
114
+ mod: A simulation.model object, the simulation results.
115
+ frames: How many frame to make for the video.
116
+
117
+ Returns:
118
+ xlim and ylim for U and V, 4 in total.
119
+ '''
120
+
121
+ # take 10 samples and store their lims in list
122
+ U_xlist = []
123
+ U_ylist = []
124
+ V_xlist = []
125
+ V_ylist = []
126
+
127
+ for i in range(10):
128
+ fig_U, ax_U = plt.subplots()
129
+ fig_V, ax_V = plt.subplots()
130
+ ax_U, ax_V = func(mod, ax_U = ax_U, ax_V = ax_V, start = i / 10, end = (i / 10 + 1 / frames))
131
+
132
+ U_xlist.append(ax_U.get_xlim())
133
+ U_ylist.append(ax_U.get_ylim())
134
+ V_xlist.append(ax_V.get_xlim())
135
+ V_ylist.append(ax_V.get_ylim())
136
+
137
+ plt.close(fig_U)
138
+ plt.close(fig_V)
139
+
140
+ # get the largest 'range' based on the lists
141
+ U_xlim = get_max_lim(U_xlist)
142
+ U_ylim = get_max_lim(U_ylist)
143
+ V_xlim = get_max_lim(V_xlist)
144
+ V_ylim = get_max_lim(V_ylist)
145
+
146
+ return U_xlim, U_ylim, V_xlim, V_ylim
147
+
148
+
149
+
150
+
151
+ def frame_heatmap_lim(mod, func, frames):
152
+ '''
153
+ Find a large enough color bar lim for frames, if heatmaps.
154
+
155
+ Inputs:
156
+ mod: A simulation.model object, the simulation results.
157
+ frames: How many frame to make for the video.
158
+
159
+ Returns:
160
+ clim for U and V
161
+ '''
162
+
163
+ U_list = []
164
+ V_list = []
165
+
166
+ for i in range(10):
167
+ fig_U, ax_U = plt.subplots()
168
+ fig_V, ax_V = plt.subplots()
169
+ ax_U, ax_V = func(mod, ax_U = ax_U, ax_V = ax_V, start = i / 10, end = (i / 10 + 1 / frames))
170
+
171
+ U_list.append(ax_U.collections[0].get_clim())
172
+ V_list.append(ax_V.collections[0].get_clim())
173
+
174
+ plt.close(fig_U)
175
+ plt.close(fig_V)
176
+
177
+ U_clim = get_max_lim(U_list)
178
+ V_clim = get_max_lim(V_list)
179
+
180
+ return U_clim, V_clim
181
+
182
+
183
+
184
+
185
+ def make_mp4(video_dir, frame_dir, fps):
186
+ '''
187
+ Read .png from the frames folder and make into a mp4
188
+ Inputs:
189
+ video_dir: where to save the video
190
+ frame_dirs: where to read frames from
191
+ fps: frames per second
192
+ '''
193
+
194
+ frame_paths_incomplete = os.listdir(frame_dir)
195
+ frame_paths_incomplete.sort(key=lambda f: int(''.join(filter(str.isdigit, f))))
196
+ frame_paths = []
197
+ for path in frame_paths_incomplete:
198
+ if (path[-4:] == '.png') and ('frame' in path):
199
+ frame_paths.append(frame_dir + '/' + path)
200
+ clip = ImageSequenceClip(frame_paths, fps = fps)
201
+ clip.write_videofile(video_dir, logger = None)
202
+
203
+
204
+
205
+
206
+ def make_video(mod, func_name = 'UV_heatmap', frames = 100, dpi = 200, fps = 30, U_color = 'Greens', V_color = 'Purples', annot = False, fmt = '.3g', del_frames = False, dirs = 'videos'):
207
+ '''
208
+ Make a mp4 video based on simulation results.
209
+
210
+ Inputs:
211
+ - mod: a simulation.model object, the simulation results.
212
+ - func_name: what function to use to make the frames. Should be one of the functions in figures.py
213
+ - frames: how many frames to make. Use more frames for more smooth evolutions.
214
+ - dpi: dots per inch.
215
+ - fps: frames per second.
216
+ - U_color: color for U's videos. Color maps or regular colors, based on what function you use.
217
+ - V_color: color for V's videos.
218
+ - annot: used by heatmaps. Whether to show numbers.
219
+ - fmt: number format
220
+ - del_frames: whether to delete frames after making video.
221
+ - dirs: where to store the frames and videos.
222
+ '''
223
+
224
+ if func_name not in FUNC_DICT.keys():
225
+ raise ValueError(func_name + ' not supported for videos.')
226
+ func = FUNC_DICT[func_name]
227
+
228
+ # convert color if invalid colors are given
229
+ U_color, V_color = convert_color(func_name, U_color, V_color)
230
+
231
+ # print progress
232
+ one_progress = frames / 100
233
+ current_progress = one_progress
234
+
235
+ if 'heatmap' in func_name:
236
+ # make sure a fixed color bar for all frames
237
+ U_clim, V_clim = frame_heatmap_lim(mod, func, frames)
238
+ else:
239
+ # make sure y axis not changing if not making heatmaps
240
+ U_xlim, U_ylim, V_xlim, V_ylim = frame_lim(mod, func, frames)
241
+
242
+
243
+ U_frame_dirs = dirs + '/U-' + func_name
244
+ V_frame_dirs = dirs + '/V-' + func_name
245
+
246
+ if os.path.exists(U_frame_dirs):
247
+ file_t.del_dirs(U_frame_dirs)
248
+ os.makedirs(U_frame_dirs)
249
+ if os.path.exists(V_frame_dirs):
250
+ file_t.del_dirs(V_frame_dirs)
251
+ os.makedirs(V_frame_dirs)
252
+
253
+ #### for loop ####
254
+
255
+ for i in range(frames):
256
+ if i > current_progress:
257
+ print('making frames', round(i / frames * 100), '%', end = '\r')
258
+ current_progress += one_progress
259
+
260
+ fig_U, ax_U = plt.subplots(figsize = (6.4, 4.8))#, constrained_layout = True)
261
+ fig_V, ax_V = plt.subplots(figsize = (6.4, 4.8))#, constrained_layout = True)
262
+
263
+ if 'heatmap' in func_name:
264
+ ax_U, ax_V = func(mod, ax_U = ax_U, ax_V = ax_V, U_color = U_color, V_color = V_color, start = i / frames, end = (i + 1) / frames, annot = annot, fmt = fmt)
265
+ else:
266
+ ax_U, ax_V = func(mod, ax_U = ax_U, ax_V = ax_V, U_color = U_color, V_color = V_color, start = i / frames, end = (i + 1) / frames)
267
+
268
+ if 'heatmap' in func_name:
269
+ ax_U.collections[0].set_clim(U_clim)
270
+ ax_V.collections[0].set_clim(V_clim)
271
+ else:
272
+ # make sure y axis not changing if not heatmap and not UV_pi
273
+ ax_U.set_ylim(U_ylim)
274
+ ax_V.set_ylim(V_ylim)
275
+ if ('hist' in func_name) or (func_name == 'UV_pi'):
276
+ # need to set xlim as well for UV_pi and histograms
277
+ ax_V.set_xlim(U_xlim)
278
+ ax_V.set_xlim(V_xlim)
279
+
280
+ fig_U.savefig(U_frame_dirs + '/' + 'U_frame_' + str(i) + '.png', pad_inches = 0.25, dpi = dpi)
281
+ fig_V.savefig(V_frame_dirs + '/' + 'V_frame_' + str(i) + '.png', pad_inches = 0.25, dpi = dpi)
282
+
283
+ plt.close(fig_U)
284
+ plt.close(fig_V)
285
+
286
+ #### for loop ends ####
287
+
288
+ # frames done
289
+ print('making mp4... ', end = '\r')
290
+
291
+ # make videos based on frames
292
+ make_mp4(dirs + '/U-' + func_name + '.mp4', U_frame_dirs, fps)
293
+ make_mp4(dirs + '/V-' + func_name + '.mp4', V_frame_dirs, fps)
294
+
295
+ if del_frames:
296
+ file_t.del_dirs(U_frame_dirs)
297
+ file_t.del_dirs(V_frame_dirs)
298
+ print('video saved: ' + dirs + ', frames deleted')
299
+ else:
300
+ print('video saved: ' + dirs + ' ')
301
+
302
+
303
+
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Chenning Xu
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.