0
|
1
|
|
2 #
|
|
3 # Copyright 2014 CIRAD
|
|
4 #
|
|
5 # This program is free software; you can redistribute it and/or modify
|
|
6 # it under the terms of the GNU General Public License as published by
|
|
7 # the Free Software Foundation; either version 3 of the License, or
|
|
8 # (at your option) any later version.
|
|
9 #
|
|
10 # This program is distributed in the hope that it will be useful,
|
|
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13 # GNU General Public License for more details.
|
|
14 #
|
|
15 # You should have received a copy of the GNU General Public License
|
|
16 # along with this program; if not, see <http://www.gnu.org/licenses/> or
|
|
17 # write to the Free Software Foundation, Inc.,
|
|
18 # 51 Franklin Street, Fifth Floor, Boston,
|
|
19 # MA 02110-1301, USA.
|
|
20 #
|
|
21 #
|
|
22
|
|
23
|
|
24 import optparse, os, shutil, subprocess, sys, tempfile, fileinput, ConfigParser, operator, time, random
|
|
25
|
|
26 def factorielle(x):
|
|
27 if x < 2:
|
|
28 return 1
|
|
29 else:
|
|
30 return x * factorielle(x-1)
|
|
31
|
|
32 def calcul_score_id(MAT, ORDRE, MARK, ORIENT, DIC_INDEX, NB):
|
|
33 #on cree l'ordre des markers
|
|
34 scaff_ordre = list(ORDRE)
|
|
35 ordre = []
|
|
36 for n in scaff_ordre:
|
|
37 liste = list(MARK[n])
|
|
38 if ORIENT[n] == 'REV':
|
|
39 liste.reverse()
|
|
40 ordre = ordre + liste
|
|
41 #on enregistre les positions relative des markers dans cet ordre
|
|
42 liste_ordre = {}
|
|
43 i = 0
|
|
44 for n in ordre:
|
|
45 liste_ordre[n] = i
|
|
46 i += 1
|
|
47 #on calcule le score
|
|
48 score = 0
|
|
49 while len(scaff_ordre) > 1:
|
|
50 scaff_fait = scaff_ordre[0]
|
|
51 del scaff_ordre[0]
|
|
52 # print scaff_fait, MARK[scaff_fait]
|
|
53 for n in MARK[scaff_fait]:
|
|
54 mark = DIC_INDEX[n]
|
|
55 for j in scaff_ordre:
|
|
56 for k in MARK[j]:
|
|
57 val = MAT[mark][k]
|
|
58 if val != 999999999:
|
|
59 score = score + ((1-(abs(liste_ordre[n]-liste_ordre[k])/NB))*val)
|
|
60 return score
|
|
61
|
|
62 def calcul_score_dif(MAT, ORDRE, MARK, ORIENT, DIC_INDEX, NB):
|
|
63 #on cree l'ordre des markers
|
|
64 scaff_ordre = list(ORDRE)
|
|
65 ordre = []
|
|
66 for n in scaff_ordre:
|
|
67 liste = list(MARK[n])
|
|
68 if ORIENT[n] == 'REV':
|
|
69 liste.reverse()
|
|
70 ordre = ordre + liste
|
|
71 #on enregistre les positions relative des markers dans cet ordre
|
|
72 liste_ordre = {}
|
|
73 i = 0
|
|
74 for n in ordre:
|
|
75 liste_ordre[n] = i
|
|
76 i += 1
|
|
77 #on calcule le score
|
|
78 score = 0
|
|
79 while len(scaff_ordre) > 1:
|
|
80 scaff_fait = scaff_ordre[0]
|
|
81 del scaff_ordre[0]
|
|
82 # print scaff_fait, MARK[scaff_fait]
|
|
83 for n in MARK[scaff_fait]:
|
|
84 mark = DIC_INDEX[n]
|
|
85 for j in scaff_ordre:
|
|
86 for k in MARK[j]:
|
|
87 val = MAT[mark][k]
|
|
88 if val != 999999999:
|
|
89 score = score + ((abs(liste_ordre[n]-liste_ordre[k])/NB)*val)
|
|
90 return score
|
|
91
|
|
92 def rear_fait(ORD, ORIENT):
|
|
93 mot = ''.join(ORD)
|
|
94 for n in ORD:
|
|
95 mot = mot+ORIENT[n]
|
|
96 return mot
|
|
97
|
|
98 def reorderient(ORDRE, ORIENT):
|
|
99 NEW_ORDRE = list(ORDRE)
|
|
100 NEW_ORIENT = {}
|
|
101 for n in ORIENT:
|
|
102 NEW_ORIENT[n] = ORIENT[n]
|
|
103 if random.randint(0,1) == 0:
|
|
104 #On travail sur 1 scaffold
|
|
105 pos = random.randint(0, (len(ORDRE)-1))
|
|
106 scaffold = NEW_ORDRE[pos]
|
|
107 if random.randint(0,1) == 0:
|
|
108 #On fait un deplacement de scaffold
|
|
109 pos_new = pos
|
|
110 while pos_new == pos:
|
|
111 pos_new = random.randint(0, (len(ORDRE)-1))
|
|
112 del NEW_ORDRE[pos]
|
|
113 if scaffold in NEW_ORDRE:
|
|
114 sys.exit('bug')
|
|
115 NEW_ORDRE.insert(pos_new, scaffold)
|
|
116 if random.randint(0,1) == 0:
|
|
117 #On fait en plus une reorientation
|
|
118 if NEW_ORIENT[scaffold] == 'FWD':
|
|
119 NEW_ORIENT[scaffold] = 'REV'
|
|
120 elif NEW_ORIENT[scaffold] == 'REV':
|
|
121 NEW_ORIENT[scaffold] = 'FWD'
|
|
122 else:
|
|
123 sys.exit('bug')
|
|
124 else:
|
|
125 #On fait juste une reorientation
|
|
126 if NEW_ORIENT[scaffold] == 'FWD':
|
|
127 NEW_ORIENT[scaffold] = 'REV'
|
|
128 elif NEW_ORIENT[scaffold] == 'REV':
|
|
129 NEW_ORIENT[scaffold] = 'FWD'
|
|
130 else:
|
|
131 sys.exit('bug')
|
|
132 else:
|
|
133 pos_debut = random.randint(0, (len(ORDRE)-2))
|
|
134 pos_fin = pos_debut
|
|
135 while (pos_debut + 1) >= pos_fin and len(NEW_ORDRE[pos_debut:pos_fin]) == len(NEW_ORDRE):
|
|
136 pos_fin = random.randint(1, len(ORDRE))
|
|
137 scaffold = NEW_ORDRE[pos_debut:pos_fin]
|
|
138 del NEW_ORDRE[pos_debut:pos_fin]
|
|
139 if random.randint(0,1) == 0:
|
|
140 #On fait un deplacement de scaffold
|
|
141 pos_new = pos_debut
|
|
142 while pos_new == pos_debut:
|
|
143 pos_new = random.randint(0, len(NEW_ORDRE))
|
|
144 if random.randint(0,1) == 0:
|
|
145 #On fait en plus une reorientation
|
|
146 for n in scaffold:
|
|
147 NEW_ORDRE.insert(pos_new, n)
|
|
148 if NEW_ORIENT[n] == 'FWD':
|
|
149 NEW_ORIENT[n] = 'REV'
|
|
150 elif NEW_ORIENT[n] == 'REV':
|
|
151 NEW_ORIENT[n] = 'FWD'
|
|
152 else:
|
|
153 sys.exit('bug')
|
|
154 else:
|
|
155 #On fait juste un deplacement
|
|
156 for n in scaffold:
|
|
157 NEW_ORDRE.insert(pos_new, n)
|
|
158 pos_new += 1
|
|
159 else:
|
|
160 #On fait juste une reorientation
|
|
161 for n in scaffold:
|
|
162 NEW_ORDRE.insert(pos_debut, n)
|
|
163 if NEW_ORIENT[n] == 'FWD':
|
|
164 NEW_ORIENT[n] = 'REV'
|
|
165 elif NEW_ORIENT[n] == 'REV':
|
|
166 NEW_ORIENT[n] = 'FWD'
|
|
167 else:
|
|
168 sys.exit('bug')
|
|
169 if len(NEW_ORDRE) != len(ORDRE):
|
|
170 sys.exit('bug')
|
|
171 return [NEW_ORDRE, NEW_ORIENT]
|
|
172
|
|
173 def __main__():
|
|
174 #Parse Command Line
|
|
175 parser = optparse.OptionParser(usage="python %prog [options]\n\nProgram designed by Guillaume MARTIN : guillaume.martin@cirad.fr\n\n"
|
|
176 "This program take in input a matrix containing maker linkage or divergence and the two output files of UPGMA.py and try to optimize the "
|
|
177 "scaffold order and orientation proposed by UPGMA.py.\n"
|
|
178 "The optimization is performed by calculating a score for the scaffold order, trying rearrangements (scaffold or scaffold group permutations "
|
|
179 "and/or inversion) followed by score re-calculation. If the new score is better than the previous, the new order is conserved; else, the previous "
|
|
180 "order is conserved. The program stops when more than a defined number of consecutive rearrangement (passed in --iter argument) do not improve the ordering.\n"
|
|
181 "This program output a file containing ordered scaffolds in a table format and a table file containing ordered markers relative to the scaffold order.\n"
|
|
182 )
|
|
183 # Wrapper options.
|
|
184 parser.add_option( '', '--mat', dest='mat', default=None, help='Matrix file with pairwise statistics between markers.')
|
|
185 parser.add_option( '', '--scaff', dest='scaff', default=None, help='The output file passed in --out1 argument of UPGMA.py. This file contains ordered and orientated scaffolds ate the end of UPGMA.py program.')
|
|
186 parser.add_option( '', '--mark', dest='mark', default=None, help='A table file containing in column 1: markers name, column2: scaffold name. This is the file generated by UPGMA.py in --out2 argument')
|
|
187 parser.add_option( '', '--iter', dest='iter', default='auto', help='Number of consecutive rearrangement tried without improvement to stop the improvement of the ordering and orientation (integer or auto). [default: %default]')
|
|
188 parser.add_option( '', '--type', dest='type', default='IDENT', help='Data type : IDENT (marker linkage) or DIF (recombination rate). [default: %default]')
|
|
189 parser.add_option( '', '--out1', dest='out1', default='UPGMA_ordered_opt_scaf.txt', help='Output file name of ordered scaffold. [default: %default]')
|
|
190 parser.add_option( '', '--out2', dest='out2', default='UPGMA_ordered_opt_mark.txt', help='Output file name of ordered markers. [default: %default]')
|
|
191 (options, args) = parser.parse_args()
|
|
192
|
|
193 if options.scaff == None:
|
|
194 sys.exit('--scaff argument is missing')
|
|
195 if options.mark == None:
|
|
196 sys.exit('--mark argument is missing')
|
|
197 if options.mat == None:
|
|
198 sys.exit('--mat argument is missing')
|
|
199
|
|
200
|
|
201 #on enregistre dans une liste les markers dans l'ordre dans la matrice
|
|
202 os.system('echo "Registering scaffold order"')
|
|
203 file = open(options.mat)
|
|
204 index_mat = file.readline().split()[1:]
|
|
205 file.close()
|
|
206
|
|
207 # On enregistre l'orientation des scaffolds
|
|
208 os.system('echo "Registering scaffold orientation"')
|
|
209 dico_orient = {}
|
|
210 file = open(options.scaff)
|
|
211 for line in file:
|
|
212 data = line.split()
|
|
213 if data:
|
|
214 dico_orient[data[0]] = data[1]
|
|
215 file.close()
|
|
216
|
|
217 #on enregistre dans un dico les scaffolds et l'ordre des positions (dans la matrice) des marqueurs dans le scaffold. Dans une liste on enregistre l'ordre des scaffolds
|
|
218 os.system('echo "Registering scaffold order and markers order"')
|
|
219 file = open(options.mark)
|
|
220 dico_mark = {}#pour chaque cle = scaffold, on enregistre l'ordre des markers dans ce scaffold
|
|
221 liste_ordre = []#Ordre des scaffolds
|
|
222 dico_index = {}#position du markers dans la matrice
|
|
223 # dico_orient = {}#######
|
|
224 nb_mark = 0
|
|
225 for line in file:
|
|
226 data = line.split()
|
|
227 if data:
|
|
228 nb_mark += 1
|
|
229 if data[1] in dico_mark:
|
|
230 dico_index[index_mat.index(data[0])] = data[0]
|
|
231 if dico_orient[data[1]] == 'FWD':
|
|
232 dico_mark[data[1]].append(index_mat.index(data[0]))
|
|
233 elif dico_orient[data[1]] == 'REV':
|
|
234 dico_mark[data[1]].insert(0, index_mat.index(data[0]))
|
|
235 else:
|
|
236 sys.exit('bugggggg')
|
|
237 else:
|
|
238 dico_mark[data[1]] = []
|
|
239 dico_mark[data[1]].append(index_mat.index(data[0]))
|
|
240 dico_index[index_mat.index(data[0])] = data[0]
|
|
241 liste_ordre.append(data[1])
|
|
242 # dico_orient[data[1]] = 'FWD'################
|
|
243 file.close()
|
|
244 nb_mark = float(nb_mark)
|
|
245
|
|
246 #On verifie le nombre d'iteration
|
|
247 total_possible = ((float(len(liste_ordre)-1)**2)*2)+(len(liste_ordre))
|
|
248 if options.iter == 'auto':
|
|
249 iteration = total_possible
|
|
250 os.system('echo "The number of iteration is automatically estimated to the number of possible iterations in case of one scaffold repositioned : '+str(total_possible)+'"')
|
|
251 elif options.iter == 'all':
|
|
252 os.system('echo "All possible rearrangments are tested"')
|
|
253 else:
|
|
254 iteration = int(options.iter)
|
|
255 if iteration > total_possible:
|
|
256 os.system('echo "The number of iteration is over the number of possible iterations in case of one scaffold repositioned. It has been ajusted to : '+str(total_possible)+' if not the script can be in a infinite loop"')
|
|
257 iteration = total_possible
|
|
258 else:
|
|
259 os.system('echo "The number of iteration is under the number of possible iterations in case of one scaffold repositioned ('+str(total_possible)+'). You choose : '+str(iteration)+'"')
|
|
260
|
|
261 #on charge la matrice dans un dico
|
|
262 os.system('echo "Recording pairwise information"')
|
|
263 matrix = {}
|
|
264 info_scaff = {}
|
|
265 file = open(options.mat)
|
|
266 file.readline()
|
|
267 for line in file:
|
|
268 data = line.split()
|
|
269 if data:
|
|
270 if data[0] in matrix:
|
|
271 sys.exit("Warning there is redundancy in markers names, verify your data")
|
|
272 else:
|
|
273 matrix[data[0]] = map(float, data[1:])
|
|
274 file.close()
|
|
275
|
|
276 if options.type == 'IDENT':
|
|
277 #On essaye toutes les orientations possibles de cet ordre
|
|
278 os.system('echo "Looking for best orientation in this order"')
|
|
279 t0 = time.clock()
|
|
280 score = calcul_score_id(matrix, liste_ordre, dico_mark, dico_orient, dico_index, nb_mark)
|
|
281 os.system('echo "Reference score : '+str(score)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
282 for n in liste_ordre:
|
|
283 t0 = time.clock()
|
|
284 dico_reor = {}
|
|
285 for j in dico_orient:
|
|
286 if j == n:
|
|
287 if dico_orient[j] == 'FWD':
|
|
288 dico_reor[j] = 'REV'
|
|
289 elif dico_orient[j] == 'REV':
|
|
290 dico_reor[j] = 'FWD'
|
|
291 else:
|
|
292 sys.exit('bug')
|
|
293 # print n, dico_reor[j], dico_orient[n]
|
|
294 else:
|
|
295 dico_reor[j] = str(dico_orient[j])
|
|
296 score_reor = calcul_score_id(matrix, liste_ordre, dico_mark, dico_reor, dico_index, nb_mark)
|
|
297 os.system('echo "Reorient score : '+str(score_reor)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
298 if score < score_reor:
|
|
299 dico_orient = {}
|
|
300 for k in dico_reor:
|
|
301 dico_orient[k] = str(dico_reor[k])
|
|
302 score = score_reor
|
|
303 #On fait des rearrangements
|
|
304 os.system('echo "Performing rearrangements"')
|
|
305 iter = 0
|
|
306 fait = set()
|
|
307 fait.add(rear_fait(liste_ordre, dico_orient))
|
|
308 while iter < iteration:
|
|
309 t0 = time.clock()
|
|
310 iter += 1
|
|
311 REOR = reorderient(liste_ordre,dico_orient)
|
|
312 j = 0
|
|
313 while rear_fait(REOR[0], REOR[1]) in fait:
|
|
314 j += 1
|
|
315 REOR = reorderient(liste_ordre,dico_orient)
|
|
316 fait.add(rear_fait(REOR[0], REOR[1]))
|
|
317 score_reor = calcul_score_id(matrix, REOR[0], dico_mark, REOR[1], dico_index, nb_mark)
|
|
318 os.system('echo "iteration : '+str(iter)+'; Reoriented score : '+str(score_reor)+', best score : '+str(score)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
319 if score < score_reor:
|
|
320 dico_orient = {}
|
|
321 for n in REOR[1]:
|
|
322 dico_orient[n] = REOR[1][n]
|
|
323 score = score_reor
|
|
324 liste_ordre = list(REOR[0])
|
|
325 iter = 0
|
|
326 fait = set()
|
|
327 fait.add(rear_fait(liste_ordre, dico_orient))
|
|
328 #On fait une derniere etape pour voir ce qui est oriente et ce qui ne l'est pas
|
|
329 os.system('echo "Looking for orientated scaffolds and those who are not"')
|
|
330 os.system('echo "Reference score : '+str(score)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
331 dico_ordre_final = {}
|
|
332 for n in liste_ordre:
|
|
333 t0 = time.clock()
|
|
334 dico_reor = {}
|
|
335 nom_ord = n
|
|
336 for j in dico_orient:
|
|
337 if j == n:
|
|
338 if dico_orient[j] == 'FWD':
|
|
339 dico_reor[j] = 'REV'
|
|
340 else:
|
|
341 dico_reor[j] = 'FWD'
|
|
342 else:
|
|
343 dico_reor[j] = dico_orient[j]
|
|
344 score_reor = calcul_score_id(matrix, liste_ordre, dico_mark, dico_reor, dico_index, nb_mark)
|
|
345 os.system('echo "Reorient score : '+str(score_reor)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
346 if score < score_reor:
|
|
347 dico_orient = {}
|
|
348 for k in dico_reor:
|
|
349 dico_orient[k] = dico_reor[k]
|
|
350 score = score_reor
|
|
351 dico_ordre_final[n] = 'Ord'
|
|
352 elif score == score_reor:
|
|
353 dico_ordre_final[n] = 'NoOrd'
|
|
354 else:
|
|
355 dico_ordre_final[n] = 'Ord'
|
|
356
|
|
357
|
|
358 elif options.type == 'DIF':
|
|
359 #On essaye toutes les orientations possibles de cet ordre
|
|
360 os.system('echo "Looking for best orientation in this order"')
|
|
361 t0 = time.clock()
|
|
362 score = calcul_score_dif(matrix, liste_ordre, dico_mark, dico_orient, dico_index, nb_mark)
|
|
363 os.system('echo "Reference score : '+str(score)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
364 for n in liste_ordre:
|
|
365 t0 = time.clock()
|
|
366 dico_reor = {}
|
|
367 for j in dico_orient:
|
|
368 if j == n:
|
|
369 if dico_orient[j] == 'FWD':
|
|
370 dico_reor[j] = 'REV'
|
|
371 elif dico_orient[j] == 'REV':
|
|
372 dico_reor[j] = 'FWD'
|
|
373 else:
|
|
374 sys.exit('bug')
|
|
375 else:
|
|
376 dico_reor[j] = dico_orient[j]
|
|
377 score_reor = calcul_score_dif(matrix, liste_ordre, dico_mark, dico_reor, dico_index, nb_mark)
|
|
378 os.system('echo "Reorient score : '+str(score_reor)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
379 if score < score_reor:
|
|
380 dico_orient = {}
|
|
381 for k in dico_reor:
|
|
382 dico_orient[k] = dico_reor[k]
|
|
383 score = score_reor
|
|
384 #On fait des rearrangements
|
|
385 os.system('echo "Performing rearrangements"')
|
|
386 iter = 0
|
|
387 fait = set()
|
|
388 fait.add(rear_fait(liste_ordre, dico_orient))
|
|
389 while iter < iteration:
|
|
390 t0 = time.clock()
|
|
391 iter += 1
|
|
392 REOR = reorderient(liste_ordre,dico_orient)
|
|
393 while rear_fait(REOR[0], REOR[1]) in fait:
|
|
394 REOR = reorderient(liste_ordre,dico_orient)
|
|
395 fait.add(rear_fait(REOR[0], REOR[1]))
|
|
396 score_reor = calcul_score_dif(matrix, REOR[0], dico_mark, REOR[1], dico_index, nb_mark)
|
|
397 os.system('echo "iteration : '+str(iter)+'; Reoriented score : '+str(score_reor)+', best score : '+str(score)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
398 if score < score_reor:
|
|
399 dico_orient = {}
|
|
400 for n in REOR[1]:
|
|
401 dico_orient[n] = REOR[1][n]
|
|
402 score = score_reor
|
|
403 liste_ordre = list(REOR[0])
|
|
404 iter = 0
|
|
405 fait = set()
|
|
406 fait.add(rear_fait(liste_ordre, dico_orient))
|
|
407 #On fait une derniere etape pour voir ce qui est oriente et ce qui ne l'est pas
|
|
408 os.system('echo "Looking for orientated scaffolds and those who are not"')
|
|
409 os.system('echo "Reference score : '+str(score)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
410 dico_ordre_final = {}
|
|
411 for n in liste_ordre:
|
|
412 t0 = time.clock()
|
|
413 dico_reor = {}
|
|
414 nom_ord = n
|
|
415 for j in dico_orient:
|
|
416 if j == n:
|
|
417 if dico_orient[j] == 'FWD':
|
|
418 dico_reor[j] = 'REV'
|
|
419 else:
|
|
420 dico_reor[j] = 'FWD'
|
|
421 else:
|
|
422 dico_reor[j] = dico_orient[j]
|
|
423 score_reor = calcul_score_dif(matrix, liste_ordre, dico_mark, dico_reor, dico_index, nb_mark)
|
|
424 os.system('echo "Reorient score : '+str(score_reor)+'; time spent in calculation : '+str(time.clock()-t0)+'"')
|
|
425 if score < score_reor:
|
|
426 dico_orient = {}
|
|
427 for k in dico_reor:
|
|
428 dico_orient[k] = dico_reor[k]
|
|
429 score = score_reor
|
|
430 dico_ordre_final[n] = 'Ord'
|
|
431 elif score == score_reor:
|
|
432 dico_ordre_final[n] = 'NoOrd'
|
|
433 if dico_orient[n] == 'REV': #
|
|
434 dico_orient = {} #
|
|
435 for k in dico_reor: #
|
|
436 dico_orient[k] = dico_reor[k] #
|
|
437 else:
|
|
438 dico_ordre_final[n] = 'Ord'
|
|
439 else:
|
|
440 sys.exit('Wrong argument passed in --type')
|
|
441
|
|
442 #On genere le fichier pour matrix to orthodother
|
|
443 os.system('echo "Outputing ordonned markers"')
|
|
444 outfile1 = open(options.out1,'w')
|
|
445 outfile = open(options.out2,'w')
|
|
446 for n in liste_ordre:
|
|
447 outfile1.write(n+'\t'+dico_orient[n]+'\t'+dico_ordre_final[n]+'\n')
|
|
448 liste = list(dico_mark[n])
|
|
449 if dico_orient[n] == 'REV':
|
|
450 liste.reverse()
|
|
451 for j in liste:
|
|
452 outfile.write(dico_index[j]+'\t'+n+'\n')
|
|
453 outfile.close()
|
|
454 outfile1.close()
|
|
455
|
|
456 if __name__ == "__main__": __main__() |