1# -*- coding: utf-8 -*-
2# ------------------------------------------------------------------------------
3# Name:         testPerformance.py
4# Purpose:      Tests keep track of long-term performance targets
5#
6# Authors:      Christopher Ariza
7#
8# Copyright:    Copyright © 2009-2011 Michael Scott Cuthbert and the music21 Project
9# License:      BSD, see license.txt
10# ------------------------------------------------------------------------------
11'''
12This module defines a number of performance test.
13 Results for these performances are stored and dated,
14 and used to track long-term performance changes.
15
16This file is not run with the standard test battery presently.
17'''
18
19
20import unittest
21
22import music21
23from music21 import common, corpus
24from music21.musicxml.m21ToXml import GeneralObjectExporter as GEX
25
26from music21 import environment
27_MOD = 'test.testPerformance'
28environLocal = environment.Environment(_MOD)
29
30# ------------------------------------------------------------------------------
31
32
33class Test(unittest.TestCase):
34
35    def runStreamIterationByIterator(self):
36        '''Stream iteration by iterator
37        '''
38        from music21 import note
39        from music21 import stream
40        # create a stream with 750 notes, 250 rests
41        s = stream.Stream()
42        for i in range(1000):
43            n = note.Note()
44            s.append(n)
45        for i in range(500):
46            r = note.Rest()
47            s.append(r)
48
49        for i in range(100):
50            for j in s:  # this will create an iterator instances
51                pass
52
53    def runStreamIterationByElements(self):
54        '''Stream iteration by .elements access
55        '''
56        from music21 import note
57        from music21 import stream
58        # create a stream with 750 notes, 250 rests
59        s = stream.Stream()
60        for i in range(1000):
61            n = note.Note()
62            s.append(n)
63        for i in range(500):
64            r = note.Rest()
65            s.append(r)
66
67        for i in range(100):
68            for j in s.elements:  # this will create an iterator instances
69                pass
70
71    def runGetElementsByClassType(self):
72        '''Getting elements by class type
73        '''
74        from music21 import note
75        from music21 import stream
76
77        # create a stream with 750 notes, 250 rests
78        s = stream.Stream()
79        for i in range(1000):
80            n = note.Note()
81            s.append(n)
82        for i in range(500):
83            r = note.Rest()
84            s.append(r)
85
86        for i in range(2):
87            post = s.recurse().getElementsByClass([note.Rest, note.Note])
88            self.assertEqual(len(post), 1500)
89
90    def runGetElementsByClassString(self):
91        '''Getting elements by string
92        '''
93        from music21 import note
94        from music21 import stream
95
96        # create a stream with 750 notes, 250 rests
97        s = stream.Stream()
98        for i in range(1000):
99            n = note.Note()
100            s.append(n)
101        for i in range(500):
102            r = note.Rest()
103            s.append(r)
104
105        for i in range(2):
106            post = s.recurse().getElementsByClass(['Rest', 'Note'])
107            self.assertEqual(len(post), 1500)
108
109    def runParseBeethoven(self):
110        '''Loading file: beethoven/opus59no2/movement3
111        '''
112        junk = corpus.parse('beethoven/opus59no2/movement3', forceSource=True)
113
114    def runMusicxmlOutPartsBeethoven(self):
115        '''Loading file and rendering musicxml output for each part: beethoven/opus59no2/movement3
116        '''
117        x = corpus.parse('beethoven/opus59no2/movement3', forceSource=True)
118        # problem: doing each part is much faster than the whole score
119        for p in x.parts:
120            junk = GEX().parse(p)
121
122    def runMusicxmlOutScoreBeethoven(self):
123        '''
124        Loading file and rendering musicxml output of complete score: beethoven/opus59no2/movement3
125        '''
126        x = corpus.parse('beethoven/opus59no2/movement3', forceSource=True)
127        # problem: doing each part is much faster than the whole score
128        junk = GEX().parse(x)
129
130    def runParseHaydn(self):
131        '''Loading file: haydn/opus74no1/movement3
132        '''
133        junk = corpus.parse('haydn/opus74no1/movement3', forceSource=True)
134
135    def runParseSchumann(self):
136        '''Loading file: schumann/opus41no1/movement2
137        '''
138        junk = corpus.parse('schumann/opus41no1/movement2', forceSource=True)
139
140    def runParseLuca(self):
141        '''
142        Loading file: luca/gloria
143        '''
144        junk = corpus.parse('luca/gloria', forceSource=True)
145
146    def runMusicxmlOutLuca(self):
147        '''
148        Loading file and rendering musicxml output: luca/gloria
149        '''
150        x = corpus.parse('luca/gloria', forceSource=True)
151        junk = GEX().parse(x)
152
153    def runCreateTimeSignatures(self):
154        '''Creating 500 TimeSignature objects
155        '''
156        from music21 import meter
157        tsStr = ['4/4', '4/4', '4/4', '3/4', '3/4', '2/4', '2/4', '2/2',
158                 '2/2', '3/8', '6/8', '9/8', '5/4', '12/8']
159
160        for i in range(500):
161            meter.TimeSignature(tsStr[i % len(tsStr)])
162
163    def runCreateDurations(self):
164        '''
165        Creating 10000 Duration objects
166        '''
167        from music21 import duration
168        qlList = [4, 2, 1, 0.5, 1 / 3, 0.25, 0.125]
169
170        for i in range(10000):
171            ql = qlList[i % len(qlList)]
172            d = duration.Duration()
173            d.quarterLength = ql
174            junk = d.quarterLength
175
176    def runCreatePitches(self):
177        '''
178        Creating 50000 Pitch objects
179        '''
180        from music21 import pitch
181        pList = [1.5, 5, 20.333333, 8, 2.5, 'A#', 'b`', 'c6#~']
182
183        for i in range(50000):
184            inputPName = pList[i % len(pList)]
185            p = pitch.Pitch(inputPName)
186            p.transpose('p5', inPlace=True)
187
188    def runParseABC(self):
189        '''Creating loading a large multiple work abc file (han1)
190        '''
191        dummy = corpus.parse('essenFolksong/han1')
192
193    def runGetElementsByContext(self):
194        '''Test getting elements by context from a Stream
195        '''
196        s = corpus.parse('bwv66.6')
197        # create a few secondary streams to add more sites
198        unused_flat = s.flatten()
199        unused_notes = s.flatten().notes
200
201        for p in s.parts:
202            for m in p.getElementsByClass('Measure'):
203                if m.number == 0:
204                    continue
205                post = m.getContextByClass('Clef')
206                assert post is not None
207                post = m.getContextByClass('TimeSignature')
208                assert post is not None
209                post = m.getContextByClass('KeySignature')
210                assert post is not None
211
212                for n in m.notesAndRests:
213                    post = n.getContextByClass('Clef')
214                    assert post is not None
215                    post = n.getContextByClass('TimeSignature')
216                    assert post is not None
217                    post = n.getContextByClass('KeySignature')
218                    assert post is not None
219
220    def runGetElementsByPrevious(self):
221        '''Test getting elements by using the previous method
222        '''
223        s = corpus.parse('bwv66.6')
224        # create a few secondary streams to add more sites
225        unused_flat = s.flatten()
226        unused_notes = s.flatten().notes
227
228        for p in s.parts:
229            for m in p.getElementsByClass('Measure'):
230                if m.number == 0:
231                    continue
232                post = m.previous('Clef')
233                assert post is not None
234                post = m.previous('TimeSignature')
235                assert post is not None
236                post = m.previous('KeySignature')
237                assert post is not None
238
239                for n in m.notesAndRests:
240                    post = n.getContextByClass('Clef')
241                    assert post is not None
242                    post = n.getContextByClass('TimeSignature')
243                    assert post is not None
244                    post = n.getContextByClass('KeySignature')
245                    assert post is not None
246
247    def runParseMonteverdiRNText(self):
248        '''Loading file: beethoven/opus59no2/movement3
249        '''
250        unused = corpus.parse('monteverdi/madrigal.5.3.rntxt', forceSource=True)
251
252    # --------------------------------------------------------------------------
253    def testTimingTolerance(self):
254        '''
255        Test the performance of methods defined above,
256        comparing the resulting time to the time obtained in past runs.
257
258        This should not produce errors as such, but is used to provide reference
259        if overall performance has changed.
260        '''
261        # provide work and expected min/max in seconds
262        for testMethod, best in [
263
264            (self.runGetElementsByPrevious,
265                {
266                    '2011.11.29': 4.69,
267                }),
268
269            (self.runGetElementsByContext,
270                {
271                    '2010.11.10': 7.3888170,
272                    '2010.11.11': 3.96121883392,
273                }),
274
275
276            #
277            #
278            #
279            # #             (self.runParseABC,
280            # #                 {
281            # #                  '2010.10.20': 38.6760668755,
282            # #                  '2010.10.21': 35.4297668934,
283            # #                 }),
284            #
285            #
286            #             (self.runCreateDurations,
287            #                 {
288            #                  '2010.10.07': 0.201117992401,
289            #
290            #                 }),
291            #
292            #             (self.runCreateTimeSignatures,
293            #                 {
294            #                  '2010.10.07': 2.88308691978,
295            #                  '2010.10.08': 1.40892004967 ,
296            #
297            #                 }),
298            #
299            #
300            #             (self.runStreamIterationByIterator,
301            #                 {'2010.09.20': 2.2524,
302            #                  '2010.10.07': 1.8214,
303            #                 }),
304            #
305            #             (self.runStreamIterationByElements,
306            #                 {'2010.09.20': 0.8317,
307            #                 }),
308            #
309            #
310            #             (self.runGetElementsByClassType,
311            #                 {'2010.09.20': 3.28,
312            #                 }),
313            #
314            #             (self.runGetElementsByClassString,
315            #                 {'2010.09.20': 3.22,
316            #                 }),
317            #
318            #             (self.runParseBeethoven,
319            #                 {'2009.12.14': 7.42,
320            #                  '2009.12.15': 6.686,
321            #                  '2010.06.24': 7.475,
322            #                  '2010.07.08': 3.562,
323            #                 }),
324            #
325            #             (self.runMusicxmlOutPartsBeethoven,
326            #                 {'2010.09.20': 7.706,
327            #                 }),
328            #
329            #             (self.runMusicxmlOutScoreBeethoven,
330            #                 {'2010.09.20': 33.273,
331            #                  '2010.10.07': 11.9290,
332            #                 }),
333
334
335            #
336            #             (self.runCreatePitches,
337            #                 {'2011.04.12': 31.071,
338            #                 }),
339
340
341            #             (self.runParseHaydn,
342            #                 {'2009.12.14': 4.08,
343            #                  '2009.12.15': 3.531,
344            #                  '2010.06.24': 3.932,
345            #                  '2010.07.08': 1.935,
346            #                 }),
347            #             (self.runParseSchumann,
348            #                 {'2009.12.14': 5.88,
349            #                  '2009.12.15': 5.126,
350            #                  '2010.06.24': 5.799,
351            #                  '2010.07.08': 2.761,
352            #                 }),
353            #             (self.runParseLuca,
354            #                 {'2009.12.14': 3.174,
355            #                  '2009.12.15': 2.954,
356            #                  '2010.06.24': 3.063,
357            #                  '2010.07.08': 1.508,
358            #                 }),
359            #
360            #             (self.runMusicxmlOutLuca,
361            #                 {'2010.09.20': 8.372,
362            #                  '2010.10.07': 4.5613,
363            #                 }),
364            #
365
366            #             (self.runParseMonteverdiRNText,
367            #                 {'2011.02.27': 6.411,
368            #                  '2011.02.28': 2.944,
369            #                 }),
370
371        ]:  # end of long for loop
372
373            t = common.Timer()
374            t.start()
375            # call the test
376            testMethod()
377            t.stop()
378            dur = t()
379            items = list(best.items())
380            items.sort()
381            items.reverse()
382            environLocal.printDebug(['\n\ntiming tolerance for:',
383                                     str(testMethod.__doc__.strip()),
384                                     '\nthis run:', dur, '\nbest runs:',
385                                     [f'{x}: {y}' for x, y in items], '\n'
386                                     ]
387                                    )
388            # self.assertEqual(True, dur <= max)  # performance test
389
390
391if __name__ == '__main__':
392    import sys
393
394    if len(sys.argv) == 1:  # normal conditions
395        # sys.arg test options will be used in mainTest()
396        music21.mainTest(Test)
397
398
399