metrolopy 0.6.2__py3-none-any.whl → 0.6.4__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.
@@ -1,349 +1,374 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ # module test_ubreakdown
4
+
5
+ # Copyright (C) 2025 National Research Council Canada
6
+ # Author: Harold Parks
7
+
8
+ # This file is part of MetroloPy.
9
+
10
+ # MetroloPy is free software: you can redistribute it and/or modify it under
11
+ # the terms of the GNU General Public License as published by the Free Software
12
+ # Foundation, either version 3 of the License, or (at your option) any later
13
+ # version.
14
+
15
+ # MetroloPy is distributed in the hope that it will be useful, but WITHOUT ANY
16
+ # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
+ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18
+ # details.
19
+
20
+ # You should have received a copy of the GNU General Public License along with
21
+ # MetroloPy. If not, see <http://www.gnu.org/licenses/>.
22
+
3
23
  import metrolopy as uc
4
24
  import numpy as np
25
+ import unittest
5
26
 
6
27
  from metrolopy.tests.common import rand,make_gummy
7
28
 
8
- def test_ubreakdown(n=None,prnt=False,budget=False):
9
- uc.gummy.p_method = None
10
-
11
- if n is None:
12
- if prnt or budget:
13
- n = 100
14
- else:
15
- n = 1000
16
-
17
- for i in range(n):
18
- k = rand.randint(1,6)
19
-
20
- if rand.randint(2):
21
- unt = '1'
22
- else:
23
- unt = None
24
- g,x,u,dof,unit,utype = zip(*[make_gummy(unit=unt) for y in range(k)])
25
-
26
- for i,y in enumerate(g):
27
- if y.u != 0:
28
- assert y.utype is utype[i]
29
-
30
- c = rand.rand()*0.9 + 0.1
31
- if rand.randint(2):
32
- c = -c
33
-
34
- def f(*p):
35
- r = c
36
- for y in p:
37
- r *= y
38
- return r
39
-
40
- def d(k,*p):
41
- r = c
42
- for i,y in enumerate(p):
43
- if i != k:
44
- r *= y
45
- return r
46
-
47
- gr = f(*g)
29
+ class TestUbreakdown(unittest.TestCase):
30
+ def test_ubreakdown(self,n=None,prnt=False,budget=False):
31
+ uc.gummy.p_method = None
48
32
 
49
- ut = None
50
- for q in g:
51
- if gr.value._ref is q.value._ref:
52
- ut = q.utype
53
- assert gr.utype is ut
54
-
55
- x = list(x)
56
- u = list(u)
57
- lb = 'lb' in str(gr.unit)
58
- kg = 'kg' in str(gr.unit)
59
- for i,un in enumerate(unit):
60
- if 'lb' in un and not lb:
61
- x[i] *= 0.45359237
62
- u[i] *= 0.45359237
63
- if 'kg' in un and not kg and lb:
64
- x[i] *= 0.45359237**4
65
- u[i] *= 0.45359237**4
66
-
67
- xr = f(*x)
68
-
69
- if xr == 0:
70
- assert abs(gr.x - xr) < 1e-10
71
- else:
72
- assert abs(gr.x - xr)/abs(xr) < 1e-10
73
-
74
- ur = 0
75
- for i,y in enumerate(u):
76
- ur += y**2*d(i,*x)**2
77
- ur = np.sqrt(ur)
78
- if ur == 0:
79
- assert abs(gr.u) < 1e-10
80
- else:
81
- assert abs(gr.u - ur)/ur < 1e-10
82
-
83
- dofr = 0
84
- for i,(v,idof) in enumerate(zip(u,dof)):
85
- dofr += v**4*d(i,*x)**4/idof
86
- if dofr == 0:
87
- dofr = float('inf')
88
- else:
89
- dofr = ur**4/dofr
90
- if dofr > uc.gummy.max_dof:
91
- dofr = float('inf')
33
+ if n is None:
34
+ if prnt or budget:
35
+ n = 100
36
+ else:
37
+ n = 1000
92
38
 
93
- # Due to the fact that any dof larger than gummy.mox_dof is rounded to
94
- # infinity in intermediate calculations, gr.dof may not be exactly dofr
95
- # particularly for large dofr.
96
- #if dofr > 100:
97
- #assert gr.dof > 100
98
- #else:
99
- #assert abs(gr.dof - dofr)/dofr < 1e-2
39
+ for i in range(n):
40
+ k = rand.randint(1,6)
100
41
 
101
- if unt == '1':
102
- if k == 1:
103
- dr = lambda *x: d(0,*x)
42
+ if rand.randint(2):
43
+ unt = '1'
104
44
  else:
105
- dr = lambda *x: [d(i,*x) for i in range(k)]
106
- ga = uc.gummy.apply(f,dr,*g)
45
+ unt = None
46
+ g,x,u,dof,unit,utype = zip(*[make_gummy(unit=unt) for y in range(k)])
47
+
48
+ for i,y in enumerate(g):
49
+ if y.u != 0:
50
+ self.assertTrue(y.utype is utype[i])
51
+
52
+ c = rand.rand()*0.9 + 0.1
53
+ if rand.randint(2):
54
+ c = -c
55
+
56
+ def f(*p):
57
+ r = c
58
+ for y in p:
59
+ r *= y
60
+ return r
61
+
62
+ def d(k,*p):
63
+ r = c
64
+ for i,y in enumerate(p):
65
+ if i != k:
66
+ r *= y
67
+ return r
68
+
69
+ gr = f(*g)
70
+
71
+ ut = None
72
+ for q in g:
73
+ if gr.value._ref is q.value._ref:
74
+ ut = q.utype
75
+ self.assertTrue(gr.utype is ut)
76
+
77
+ x = list(x)
78
+ u = list(u)
79
+ lb = 'lb' in str(gr.unit)
80
+ kg = 'kg' in str(gr.unit)
81
+ for i,un in enumerate(unit):
82
+ if 'lb' in un and not lb:
83
+ x[i] *= 0.45359237
84
+ u[i] *= 0.45359237
85
+ if 'kg' in un and not kg and lb:
86
+ x[i] *= 0.45359237**4
87
+ u[i] *= 0.45359237**4
88
+
89
+ xr = f(*x)
90
+
107
91
  if xr == 0:
108
- assert abs(ga.x) < 1e-10
92
+ self.assertTrue(abs(gr.x - xr) < 1e-10)
109
93
  else:
110
- assert abs(ga.x - xr)/abs(xr) < 1e-10
94
+ self.assertTrue(abs(gr.x - xr)/abs(xr) < 1e-10)
95
+
96
+ ur = 0
97
+ for i,y in enumerate(u):
98
+ ur += y**2*d(i,*x)**2
99
+ ur = np.sqrt(ur)
111
100
  if ur == 0:
112
- assert abs(ga.u) < 1e-6
101
+ self.assertTrue(abs(gr.u) < 1e-10)
113
102
  else:
114
- assert abs(ga.u - ur)/ur < 1e-6
115
- if dofr > 100:
116
- assert ga.dof > 100
103
+ self.assertTrue(abs(gr.u - ur)/ur < 1e-10)
104
+
105
+ dofr = 0
106
+ for i,(v,idof) in enumerate(zip(u,dof)):
107
+ dofr += v**4*d(i,*x)**4/idof
108
+ if dofr == 0:
109
+ dofr = float('inf')
117
110
  else:
118
- assert abs(ga.dof - dofr)/dofr < 1e-2
111
+ dofr = ur**4/dofr
112
+ if dofr > uc.gummy.max_dof:
113
+ dofr = float('inf')
119
114
 
120
- gn = uc.gummy.napply(f,*g)
121
- if xr == 0:
122
- assert abs(gn.x) < 1e-10
123
- else:
124
- assert abs(gn.x - xr)/abs(xr) < 1e-10
125
- if ur == 0:
126
- assert abs(gn.u) < 1e-3
127
- else:
128
- assert abs(gn.u - ur)/ur < 1e-3
129
- if dofr > 100:
130
- assert gn.dof > 100
131
- else:
132
- assert abs(gn.dof - dofr)/dofr < 1e-2
115
+ # Due to the fact that any dof larger than gummy.mox_dof is rounded to
116
+ # infinity in intermediate calculations, gr.dof may not be exactly dofr
117
+ # particularly for large dofr.
118
+ #if dofr > 100:
119
+ #self.assertTrue(gr.dof > 100
120
+ #else:
121
+ #self.assertTrue(abs(gr.dof - dofr)/dofr < 1e-2
133
122
 
134
- if rand.randint(2):
123
+ if unt == '1':
124
+ if k == 1:
125
+ dr = lambda *x: d(0,*x)
126
+ else:
127
+ dr = lambda *x: [d(i,*x) for i in range(k)]
128
+ ga = uc.gummy.apply(f,dr,*g)
129
+ if xr == 0:
130
+ self.assertTrue(abs(ga.x) < 1e-10)
131
+ else:
132
+ self.assertTrue(abs(ga.x - xr)/abs(xr) < 1e-10)
133
+ if ur == 0:
134
+ self.assertTrue(abs(ga.u) < 1e-6)
135
+ else:
136
+ self.assertTrue(abs(ga.u - ur)/ur < 1e-6)
137
+ if dofr > 100:
138
+ self.assertTrue(ga.dof > 100)
139
+ else:
140
+ self.assertTrue(abs(ga.dof - dofr)/dofr < 1e-2)
141
+
142
+ gn = uc.gummy.napply(f,*g)
143
+ if xr == 0:
144
+ self.assertTrue(abs(gn.x) < 1e-10)
145
+ else:
146
+ self.assertTrue(abs(gn.x - xr)/abs(xr) < 1e-10)
147
+ if ur == 0:
148
+ self.assertTrue(abs(gn.u) < 1e-3)
149
+ else:
150
+ self.assertTrue(abs(gn.u - ur)/ur < 1e-3)
151
+ if dofr > 100:
152
+ self.assertTrue(gn.dof > 100)
153
+ else:
154
+ self.assertTrue(abs(gn.dof - dofr)/dofr < 1e-2)
155
+
135
156
  if rand.randint(2):
136
- gf = ga
157
+ if rand.randint(2):
158
+ gf = ga
159
+ else:
160
+ gf = gn
137
161
  else:
138
- gf = gn
162
+ gf = gr
139
163
  else:
140
164
  gf = gr
141
- else:
142
- gf = gr
143
-
144
- gl = list(g)
145
- if not rand.randint(4):
146
- gl.append(make_gummy(unit=unt)[0])
147
-
148
- if rand.randint(2):
149
- e = gl[rand.randint(len(gl))]
150
- else:
151
- e = [gl[rand.randint(len(gl))] for i in range(rand.randint(len(gl)))]
152
-
153
- if ur > 0:
154
- ua = 0
155
- ub = 0
156
- ud = 0
157
- ue = 0
158
- dofa = 0
159
- dofb = 0
160
- dofd = 0
161
- dofe = 0
162
- for i,(ig,iu,idof) in enumerate(zip(g,u,dof)):
163
- if gf.ufrom(ig) == 0:
164
- assert iu*d(i,*x)/ur < 1e-3
165
- else:
166
- if ig.utype == 'A':
167
- ua += iu**2*d(i,*x)**2
168
- dofa += iu**4*d(i,*x)**4/idof
169
- elif ig.utype == 'B':
170
- ub += iu**2*d(i,*x)**2
171
- dofb += iu**4*d(i,*x)**4/idof
172
- elif ig.utype == 'D':
173
- ud += iu**2*d(i,*x)**2
174
- dofd += iu**4*d(i,*x)**4/idof
175
-
176
- try:
177
- if e is ig or ig in e:
178
- ue += iu**2*d(i,*x)**2
179
- dofe += iu**4*d(i,*x)**4/idof
180
- except TypeError:
181
- pass
182
165
 
183
- ua = np.sqrt(ua)
184
- ub = np.sqrt(ub)
185
- ud = np.sqrt(ud)
186
- ue = np.sqrt(ue)
187
-
188
- if dofa == 0:
189
- dofa = float('inf')
190
- else:
191
- dofa = ua**4/dofa
192
- if dofb == 0:
193
- dofb = float('inf')
194
- else:
195
- dofb = ub**4/dofb
196
- if dofd == 0:
197
- dofd = float('inf')
198
- else:
199
- dofd = ud**4/dofd
200
- if dofe == 0:
201
- dofe = float('inf')
166
+ gl = list(g)
167
+ if not rand.randint(4):
168
+ gl.append(make_gummy(unit=unt)[0])
169
+
170
+ if rand.randint(2):
171
+ e = gl[rand.randint(len(gl))]
202
172
  else:
203
- dofe = ue**4/dofe
173
+ e = [gl[rand.randint(len(gl))] for i in range(rand.randint(len(gl)))]
174
+
175
+ if ur > 0:
176
+ ua = 0
177
+ ub = 0
178
+ ud = 0
179
+ ue = 0
180
+ dofa = 0
181
+ dofb = 0
182
+ dofd = 0
183
+ dofe = 0
184
+ for i,(ig,iu,idof) in enumerate(zip(g,u,dof)):
185
+ if gf.ufrom(ig) == 0:
186
+ self.assertTrue(iu*d(i,*x)/ur < 1e-3)
187
+ else:
188
+ if ig.utype == 'A':
189
+ ua += iu**2*d(i,*x)**2
190
+ dofa += iu**4*d(i,*x)**4/idof
191
+ elif ig.utype == 'B':
192
+ ub += iu**2*d(i,*x)**2
193
+ dofb += iu**4*d(i,*x)**4/idof
194
+ elif ig.utype == 'D':
195
+ ud += iu**2*d(i,*x)**2
196
+ dofd += iu**4*d(i,*x)**4/idof
204
197
 
205
- if ua == 0:
206
- assert gf.ufrom('A') == 0
207
- else:
208
- assert abs(gf.ufrom('A') - ua)/ur < 1e-3
209
- if ua/ur > 0.01:
210
- if dofa > 100 or gf.ufrom('A') == 0:
211
- assert gf.doffrom('A') > 100
212
- else:
213
- assert abs(gf.doffrom('A') - dofa)/dofa < 1e-2
198
+ try:
199
+ if e is ig or ig in e:
200
+ ue += iu**2*d(i,*x)**2
201
+ dofe += iu**4*d(i,*x)**4/idof
202
+ except TypeError:
203
+ pass
204
+
205
+ ua = np.sqrt(ua)
206
+ ub = np.sqrt(ub)
207
+ ud = np.sqrt(ud)
208
+ ue = np.sqrt(ue)
214
209
 
215
- if ub == 0:
216
- assert gf.ufrom('B') == 0
217
- else:
218
- assert abs(gf.ufrom('B') - ub)/ur < 1e-3
219
- if ub/ur > 0.01:
220
- if dofb > 100 or gf.ufrom('B') == 0:
221
- assert gf.doffrom('B') > 100
210
+ if dofa == 0:
211
+ dofa = float('inf')
222
212
  else:
223
- assert abs(gf.doffrom('B') - dofb)/dofb < 1e-2
224
-
225
- if ud == 0:
226
- assert gf.ufrom('D') == 0
227
- else:
228
- assert abs(gf.ufrom('D') - ud)/ur < 1e-3
229
- if ud/ur > 0.01:
230
- if dofd > 100 or gf.ufrom('D') == 0:
231
- assert gf.doffrom('D') > 100
213
+ dofa = ua**4/dofa
214
+ if dofb == 0:
215
+ dofb = float('inf')
232
216
  else:
233
- assert abs(gf.doffrom('D') - dofd)/dofd < 1e-2
234
-
235
- if ue == 0:
236
- assert gf.ufrom(e) == 0
237
- else:
238
- assert abs(gf.ufrom(e) - ue)/ur < 1e-3
239
- #if ue/ur > 0.01:
240
- #if dofe > 100 or gf.ufrom(e) == 0:
241
- #assert gf.doffrom(e) > 100
242
- #else:
243
- #assert abs(gf.doffrom(e) - dofe)/dofe < 1e-2
217
+ dofb = ub**4/dofb
218
+ if dofd == 0:
219
+ dofd = float('inf')
220
+ else:
221
+ dofd = ud**4/dofd
222
+ if dofe == 0:
223
+ dofe = float('inf')
224
+ else:
225
+ dofe = ue**4/dofe
226
+
227
+ if ua == 0:
228
+ self.assertTrue(gf.ufrom('A') == 0)
229
+ else:
230
+ self.assertTrue(abs(gf.ufrom('A') - ua)/ur < 1e-3)
231
+ if ua/ur > 0.01:
232
+ if dofa > 100 or gf.ufrom('A') == 0:
233
+ self.assertTrue(gf.doffrom('A') > 100)
234
+ else:
235
+ self.assertTrue(abs(gf.doffrom('A') - dofa)/dofa < 1e-2)
244
236
 
245
- if not rand.randint(4):
246
- gf.uunit = '%'
247
-
248
- styles = ['pm','pmi','concise','ueq','u','uf']
249
- gf.style = styles[rand.randint(6)]
250
-
251
- assert '?' not in gf.tostring()
252
-
253
- ub = {i.utype for i in g if i.utype is not None}
254
- if not rand.randint(8) and len(ub) > 1:
255
- ub.pop()
256
- ub = list(ub)
257
-
258
- gfub = gf.copy(formatting=True)
259
- if len(ub) > 0:
260
- gfub.ubreakdown = ub
261
-
262
- assert '?' not in gfub.tostring()
263
-
264
- if prnt:
265
- w = rand.randint(5)
266
- if w == 0:
267
- print(gf)
268
- print(gfub)
269
- elif w == 1:
270
- gf.latex()
271
- gfub.latex()
272
- elif w == 2:
273
- gf.html()
274
- gfub.html()
275
- elif w == 3:
276
- gf.unicode()
277
- gfub.unicode()
278
- else:
279
- gf.ascii()
280
- gfub.ascii()
281
-
282
- if budget:
283
- show_expanded_u = bool(rand.randint(2))
284
- show_subtotals = bool(rand.randint(2))
285
-
286
- k = None
287
- p = None
288
- if bool(rand.randint(2)):
289
- if bool(rand.randint(2)):
290
- if bool(rand.randint(2)):
291
- gf.k = 2
237
+ if ub == 0:
238
+ self.assertTrue(gf.ufrom('B') == 0)
239
+ else:
240
+ self.assertTrue(abs(gf.ufrom('B') - ub)/ur < 1e-3)
241
+ if ub/ur > 0.01:
242
+ if dofb > 100 or gf.ufrom('B') == 0:
243
+ self.assertTrue(gf.doffrom('B') > 100)
292
244
  else:
293
- k = 2
245
+ self.assertTrue(abs(gf.doffrom('B') - dofb)/dofb < 1e-2)
246
+
247
+ if ud == 0:
248
+ self.assertTrue(gf.ufrom('D') == 0)
294
249
  else:
295
- if bool(rand.randint(2)):
296
- gf.p = 0.95
250
+ self.assertTrue(abs(gf.ufrom('D') - ud)/ur < 1e-3)
251
+ if ud/ur > 0.01:
252
+ if dofd > 100 or gf.ufrom('D') == 0:
253
+ self.assertTrue(gf.doffrom('D') > 100)
297
254
  else:
298
- p = 0.95
255
+ self.assertTrue(abs(gf.doffrom('D') - dofd)/dofd < 1e-2)
256
+
257
+ if ue == 0:
258
+ self.assertTrue(gf.ufrom(e) == 0)
259
+ else:
260
+ self.assertTrue(abs(gf.ufrom(e) - ue)/ur < 1e-3)
261
+ #if ue/ur > 0.01:
262
+ #if dofe > 100 or gf.ufrom(e) == 0:
263
+ #self.assertTrue(gf.doffrom(e) > 100
264
+ #else:
265
+ #self.assertTrue(abs(gf.doffrom(e) - dofe)/dofe < 1e-2
299
266
 
300
- if bool(rand.randint(2)):
301
- gl[0].name = 'gl0'
302
-
303
- if bool(rand.randint(2)) and len(gl) > 1:
304
- gl[1].name = 'gl1'
305
-
306
- if bool(rand.randint(2)):
307
- gf.name = 'gf'
267
+ if not rand.randint(4):
268
+ gf.uunit = '%'
269
+
270
+ styles = ['pm','pmi','concise','ueq','u','uf']
271
+ gf.style = styles[rand.randint(6)]
272
+
273
+ self.assertTrue('?' not in gf.tostring())
308
274
 
309
- if not bool(rand.randint(4)):
310
- description = ['description ' + str(i) for i in range(len(gl)+1)]
311
- else:
312
- description = None
275
+ ub = {i.utype for i in g if i.utype is not None}
276
+ if not rand.randint(8) and len(ub) > 1:
277
+ ub.pop()
278
+ ub = list(ub)
279
+
280
+ gfub = gf.copy(formatting=True)
281
+ if len(ub) > 0:
282
+ gfub.ubreakdown = ub
283
+
284
+ self.assertTrue('?' not in gfub.tostring())
285
+
286
+ if prnt:
287
+ w = rand.randint(5)
288
+ if w == 0:
289
+ print(gf)
290
+ print(gfub)
291
+ elif w == 1:
292
+ gf.latex()
293
+ gfub.latex()
294
+ elif w == 2:
295
+ gf.html()
296
+ gfub.html()
297
+ elif w == 3:
298
+ gf.unicode()
299
+ gfub.unicode()
300
+ else:
301
+ gf.ascii()
302
+ gfub.ascii()
303
+
304
+ if budget:
305
+ show_expanded_u = bool(rand.randint(2))
306
+ show_subtotals = bool(rand.randint(2))
313
307
 
314
- if not bool(rand.randint(4)):
315
- custom = [str(i) for i in range(len(gl)+1)]
316
- custom_heading = 'n'
317
- else:
318
- custom = None
319
- custom_heading = None
308
+ k = None
309
+ p = None
310
+ if bool(rand.randint(2)):
311
+ if bool(rand.randint(2)):
312
+ if bool(rand.randint(2)):
313
+ gf.k = 2
314
+ else:
315
+ k = 2
316
+ else:
317
+ if bool(rand.randint(2)):
318
+ gf.p = 0.95
319
+ else:
320
+ p = 0.95
321
+
322
+ if bool(rand.randint(2)):
323
+ gl[0].name = 'gl0'
324
+
325
+ if bool(rand.randint(2)) and len(gl) > 1:
326
+ gl[1].name = 'gl1'
327
+
328
+ if bool(rand.randint(2)):
329
+ gf.name = 'gf'
320
330
 
321
- show_s = None
322
- show_c = None
323
- show_d = None
324
- if bool(rand.randint(2)):
325
- show_s = bool(rand.randint(2))
326
- show_c = bool(rand.randint(2))
327
- show_d = bool(rand.randint(2))
331
+ if not bool(rand.randint(4)):
332
+ description = ['description ' + str(i) for i in range(len(gl)+1)]
333
+ else:
334
+ description = None
335
+
336
+ if not bool(rand.randint(4)):
337
+ custom = [str(i) for i in range(len(gl)+1)]
338
+ custom_heading = 'n'
339
+ else:
340
+ custom = None
341
+ custom_heading = None
342
+
343
+ show_s = None
344
+ show_c = None
345
+ show_d = None
346
+ if bool(rand.randint(2)):
347
+ show_s = bool(rand.randint(2))
348
+ show_c = bool(rand.randint(2))
349
+ show_d = bool(rand.randint(2))
350
+
351
+ b = gf.budget(gl,show_subtotals=show_subtotals,
352
+ show_expanded_u=show_expanded_u,k=k,p=p,
353
+ description=description,show_s=show_s,show_c=show_c,
354
+ show_d=show_d,custom=custom,
355
+ custom_heading=custom_heading)
328
356
 
329
- b = gf.budget(gl,show_subtotals=show_subtotals,
330
- show_expanded_u=show_expanded_u,k=k,p=p,
331
- description=description,show_s=show_s,show_c=show_c,
332
- show_d=show_d,custom=custom,
333
- custom_heading=custom_heading)
357
+ w = rand.randint(5)
358
+ if w == 0:
359
+ print(b)
360
+ elif w == 1:
361
+ b.latex()
362
+ elif w == 2:
363
+ b.html()
364
+ else:
365
+ b.unicode()
334
366
 
335
- w = rand.randint(5)
336
- if w == 0:
337
- print(b)
338
- elif w == 1:
339
- b.latex()
340
- elif w == 2:
341
- b.html()
342
- else:
343
- b.unicode()
344
-
345
- if prnt or budget:
346
- print()
347
- print('---')
348
- print()
367
+ if prnt or budget:
368
+ print()
369
+ print('---')
370
+ print()
371
+
349
372
 
373
+ if __name__ == '__main__':
374
+ unittest.main()