Skip to content

Commit fb1af8b

Browse files
committed
Merge branch 'master' into burkland/split_up_main_source_file
2 parents 925d191 + 55e19ee commit fb1af8b

8 files changed

Lines changed: 718 additions & 1 deletion

File tree

README.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@ ArrayKit requires the following:
3737
What is New in ArrayKit
3838
-------------------------
3939

40+
41+
0.7.2
42+
............
43+
44+
Improved ``array_to_tuple_array()`` and ``array_to_tuple_iter()`` to preserve ``tuple`` in 1D arrays.
45+
46+
47+
0.7.1
48+
............
49+
50+
Extended ``array_to_tuple_array()`` and ``array_to_tuple_iter()`` to support 1D arrays.
51+
52+
53+
0.7.0
54+
............
55+
56+
Added ``array_to_tuple_array()``.
57+
58+
Added ``array_to_tuple_iter()``.
59+
60+
4061
0.6.3
4162
............
4263

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import os
2+
import sys
3+
import timeit
4+
import typing as tp
5+
6+
from arraykit import array_to_tuple_array
7+
import arraykit as ak
8+
9+
import matplotlib.pyplot as plt
10+
import numpy as np
11+
import pandas as pd
12+
13+
sys.path.append(os.getcwd())
14+
15+
class ArrayProcessor:
16+
NAME = ''
17+
SORT = -1
18+
19+
def __init__(self, array: np.ndarray):
20+
self.array = array
21+
22+
#-------------------------------------------------------------------------------
23+
class AKArray2D1D(ArrayProcessor):
24+
NAME = 'ak.array_to_tuple_array()'
25+
SORT = 0
26+
27+
def __call__(self):
28+
_ = array_to_tuple_array(self.array)
29+
30+
class PyArray2D1D(ArrayProcessor):
31+
NAME = 'Python construction'
32+
SORT = 1
33+
34+
def __call__(self):
35+
post = np.empty(self.array.shape[0], dtype=object)
36+
if self.array.ndim == 1:
37+
for i, e in enumerate(self.array):
38+
post[i] = (e,)
39+
else:
40+
for i, row in enumerate(self.array):
41+
post[i] = tuple(row)
42+
post.flags.writeable = False
43+
44+
#-------------------------------------------------------------------------------
45+
NUMBER = 200
46+
47+
def seconds_to_display(seconds: float) -> str:
48+
seconds /= NUMBER
49+
if seconds < 1e-4:
50+
return f'{seconds * 1e6: .1f} (µs)'
51+
if seconds < 1e-1:
52+
return f'{seconds * 1e3: .1f} (ms)'
53+
return f'{seconds: .1f} (s)'
54+
55+
56+
def plot_performance(frame):
57+
fixture_total = len(frame['fixture'].unique())
58+
cat_total = len(frame['size'].unique())
59+
processor_total = len(frame['cls_processor'].unique())
60+
fig, axes = plt.subplots(cat_total, fixture_total)
61+
62+
# cmap = plt.get_cmap('terrain')
63+
cmap = plt.get_cmap('plasma')
64+
65+
color = cmap(np.arange(processor_total) / max(processor_total, 3))
66+
67+
# category is the size of the array
68+
for cat_count, (cat_label, cat) in enumerate(frame.groupby('size')):
69+
# each fixture is a collection of tests for one display
70+
fixtures = {fixture_label: fixture for fixture_label, fixture in cat.groupby('fixture')}
71+
for fixture_count, (fixture_label, fixture) in enumerate(
72+
(k, fixtures[k]) for k in FixtureFactory.DENSITY_TO_DISPLAY):
73+
ax = axes[cat_count][fixture_count]
74+
75+
# set order
76+
fixture['sort'] = [f.SORT for f in fixture['cls_processor']]
77+
fixture = fixture.sort_values('sort')
78+
79+
results = fixture['time'].values.tolist()
80+
names = [cls.NAME for cls in fixture['cls_processor']]
81+
# x = np.arange(len(results))
82+
names_display = names
83+
post = ax.bar(names_display, results, color=color)
84+
85+
# density, position = fixture_label.split('-')
86+
# cat_label is the size of the array
87+
title = f'{cat_label:.0e}\n{FixtureFactory.DENSITY_TO_DISPLAY[fixture_label]}'
88+
89+
ax.set_title(title, fontsize=6)
90+
ax.set_box_aspect(0.75) # makes taller than wide
91+
time_max = fixture['time'].max()
92+
ax.set_yticks([0, time_max * 0.5, time_max])
93+
ax.set_yticklabels(['',
94+
seconds_to_display(time_max * .5),
95+
seconds_to_display(time_max),
96+
], fontsize=4)
97+
# ax.set_xticks(x, names_display, rotation='vertical')
98+
ax.tick_params(
99+
axis='x',
100+
which='both',
101+
bottom=False,
102+
top=False,
103+
labelbottom=False,
104+
)
105+
106+
fig.set_size_inches(8, 4) # width, height
107+
fig.legend(post, names_display, loc='center right', fontsize=6)
108+
# horizontal, vertical
109+
fig.text(.05, .96, f'array_to_tuple_array() Performance: {NUMBER} Iterations', fontsize=10)
110+
fig.text(.05, .90, get_versions(), fontsize=6)
111+
112+
fp = '/tmp/array_to_tuple_array.png'
113+
plt.subplots_adjust(
114+
left=0.05,
115+
bottom=0.05,
116+
right=0.8,
117+
top=0.85,
118+
wspace=1.0, # width
119+
hspace=0.5,
120+
)
121+
# plt.rcParams.update({'font.size': 22})
122+
plt.savefig(fp, dpi=300)
123+
124+
if sys.platform.startswith('linux'):
125+
os.system(f'eog {fp}&')
126+
else:
127+
os.system(f'open {fp}')
128+
129+
130+
#-------------------------------------------------------------------------------
131+
132+
class FixtureFactory:
133+
NAME = ''
134+
135+
@staticmethod
136+
def get_array(size: int, width_ratio: int) -> np.ndarray:
137+
if width_ratio > 1:
138+
return np.arange(size).reshape(size // width_ratio, width_ratio)
139+
return np.arange(size) # return 1D array
140+
141+
@classmethod
142+
def get_label_array(cls, size: int) -> tp.Tuple[str, np.ndarray]:
143+
array = cls.get_array(size)
144+
return cls.NAME, array
145+
146+
DENSITY_TO_DISPLAY = {
147+
'column-1': '1 Column',
148+
'column-2': '2 Column',
149+
'column-5': '5 Column',
150+
'column-10': '10 Column',
151+
'column-20': '20 Column',
152+
}
153+
154+
# POSITION_TO_DISPLAY = {
155+
# 'first_third': 'Fill 1/3 to End',
156+
# 'second_third': 'Fill 2/3 to End',
157+
# }
158+
159+
160+
class FFC1(FixtureFactory):
161+
NAME = 'column-1'
162+
163+
@staticmethod
164+
def get_array(size: int) -> np.ndarray:
165+
a = FixtureFactory.get_array(size, 1)
166+
return a
167+
168+
169+
class FFC2(FixtureFactory):
170+
NAME = 'column-2'
171+
172+
@staticmethod
173+
def get_array(size: int) -> np.ndarray:
174+
a = FixtureFactory.get_array(size, 2)
175+
return a
176+
177+
class FFC5(FixtureFactory):
178+
NAME = 'column-5'
179+
180+
@staticmethod
181+
def get_array(size: int) -> np.ndarray:
182+
a = FixtureFactory.get_array(size, 5)
183+
return a
184+
185+
class FFC10(FixtureFactory):
186+
NAME = 'column-10'
187+
188+
@staticmethod
189+
def get_array(size: int) -> np.ndarray:
190+
a = FixtureFactory.get_array(size, 10)
191+
return a
192+
193+
class FFC20(FixtureFactory):
194+
NAME = 'column-20'
195+
196+
@staticmethod
197+
def get_array(size: int) -> np.ndarray:
198+
a = FixtureFactory.get_array(size, 20)
199+
return a
200+
201+
def get_versions() -> str:
202+
import platform
203+
return f'OS: {platform.system()} / ArrayKit: {ak.__version__} / NumPy: {np.__version__}\n'
204+
205+
206+
CLS_PROCESSOR = (
207+
AKArray2D1D,
208+
PyArray2D1D,
209+
)
210+
211+
CLS_FF = (
212+
FFC1,
213+
FFC2,
214+
FFC5,
215+
FFC10,
216+
FFC20,
217+
)
218+
219+
220+
def run_test():
221+
records = []
222+
for size in (1_000, 10_000, 100_000, 1_000_000):
223+
for ff in CLS_FF:
224+
fixture_label, fixture = ff.get_label_array(size)
225+
for cls in CLS_PROCESSOR:
226+
runner = cls(fixture)
227+
228+
record = [cls, NUMBER, fixture_label, size]
229+
print(record)
230+
try:
231+
result = timeit.timeit(
232+
f'runner()',
233+
globals=locals(),
234+
number=NUMBER)
235+
except OSError:
236+
result = np.nan
237+
finally:
238+
pass
239+
record.append(result)
240+
records.append(record)
241+
242+
f = pd.DataFrame.from_records(records,
243+
columns=('cls_processor', 'number', 'fixture', 'size', 'time')
244+
)
245+
print(f)
246+
plot_performance(f)
247+
248+
if __name__ == '__main__':
249+
250+
run_test()
251+
252+
253+

0 commit comments

Comments
 (0)