-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathold_datasheet.py
More file actions
executable file
·603 lines (549 loc) · 23.6 KB
/
old_datasheet.py
File metadata and controls
executable file
·603 lines (549 loc) · 23.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
#!/home/jgarne01/anaconda3/bin/python3.6
# simulation class to instantiate and update simulation times
import os
import sys
class DataSheet(object):
"""
"""
def __init__(self):
""" note: length of each header item should not exceed 26 characters
to avoid formatting issues
"""
self.header = "'Simulation Name', 'WT/Mu', 'Protein Name', 'Bacteria Name', 'Residue Range', 'Salt Type', 'Salt Concentration', 'Extra Comments', 'Progress (ns)', 'Server Name', 'Source Directory'\n"
self.header_list = ['Simulation Name', 'WT/Mu', 'Protein Name', 'Bacteria Name', 'Residue Range', 'Salt Type', 'Salt Concentration', 'Extra Comments', 'Progress (ns)', 'Server Name', 'Source Directory']
self.category_functions = self.make_category_functions()
self.header_length = len(self.header_list)
self.fname = self.get_filename()
self.num_simulations = self.count_sim()
self.main_dict = self.read_datafile()
self.simulation_names = self.get_sim_names()
def make_category_functions(self):
""" all items in header_list are put into a dictionary
the value of each key is the function that changes the
key for a given simulation
example: "Simulation Name" is changed by calling the
function self.get_sim_name()
This should be changed if the user adds more categories
DO NOT change the index for get_sim_name or put another
category before it
"""
self.category_functions = {}
self.category_functions[self.header_list[0]] = "get_sim_name"
self.category_functions[self.header_list[1]] = "get_sim_type"
self.category_functions[self.header_list[2]] = "get_protein_name"
self.category_functions[self.header_list[3]] = "get_protein_bacteria"
self.category_functions[self.header_list[4]] = "get_res_range"
self.category_functions[self.header_list[5]] = "get_salt_name"
self.category_functions[self.header_list[6]] = "get_salt_conc"
self.category_functions[self.header_list[7]] = "get_comments"
self.category_functions[self.header_list[8]] = "get_sim_length"
self.category_functions[self.header_list[9]] = "get_sim_server"
self.category_functions[self.header_list[10]] = "get_sim_dir"
return self.category_functions
def get_filename(self):
""" the user will be prompted for a name to same simulation
data to. this file will also be used to pull out current
simulation progress. if no filename is given, the default
is set to "simulation_times.csv".
"""
print("Enter a filename for storing information. The default filename is \"simulation_times.csv\". Press enter to use the default filename: ")
fname = input()
if fname == '':
print("Data will be stored in simulation_times.csv.")
return "simulation_times.csv"
else:
print("Data will be stored in "+fname+'.')
return fname
def count_sim(self):
""" determines how many simulations currently exist
in the fname given
"""
# if fname does not exist, return 0 simulations
if os.path.isfile(self.fname) == False:
return 0
# check if file is empty
elif os.path.getsize(self.fname) == 0:
return 0
# if not empty, ignore header, count the number of simulations
else:
with open(self.fname, 'r') as f:
first = True
counter = 0
for line in f:
if first == True:
first = False
continue
else:
counter+=1
return counter
def get_sim_names(self):
""" returns empty list if there is no data in fname
needs to be updated AFTER the dictionary has been updated
as it uses the dictionary to pull out simulation names
"""
if self.count_sim() == 0 and self.num_simulations == 0:
print("No data has been written yet to "+self.fname+'.')
return []
else:
print("Data will now be read from file "+self.fname+'.')
self.simulation_names = []
for key in sorted(self.main_dict):
self.simulation_names.append(key)
return self.simulation_names
def read_datafile(self):
""" pulls all data from the fname file and stores it
in a dictionary
"""
# avoids trying to open non-existent file
if self.count_sim() == 0:
return {}
header = True
self.main_dict = {}
with open(self.fname, 'r', encoding='utf-8') as f:
for line in f:
if header == True: # ignore header
header = False
else:
try: # if no commas, return error
sim_name = line[1:line.index(',')-1]
values = line[:-1].split(', ')
except ValueError as e:
print(e)
print("There may be commas missing from the file for line:")
print(line)
print("Please reformat this entry to avoid data loss, then restart the program.")
sys.exit()
else:
# gross formatting crap
# text is indicated by ' '
# may have commas within text
data = []
start = True
entry = ''
add_entry = False
for item in values:
if item.startswith("'") and item.endswith("'"):
data.append(item[1:-1])
elif start == True and item.startswith("'"):
entry+=item
entry+=', '
start = False
add_entry = True
elif start == False and add_entry == True and item.endswith("'"):
entry+=item
data.append(entry[1:-1])
start = True
add_entry = False
elif start == False and add_entry == True:
entry+=item
entry+=', '
elif "'" in item:
data.append(item)
else:
try:
data.append(float(item))
except ValueError as e:
print(e)
sim_name = data[0]
self.main_dict[sim_name] = data
# if data entry has bad length, return error, exit program
if len(data) != self.header_length:
print("Unable to read line:")
print(data)
print('Line has', str(len(data)), 'entries, and needs', str(self.header_length), sep=' ')
print("Please reformat this entry to avoid data loss, then restart the program.")
sys.exit()
else:
return self.main_dict
def print_sim_names(self):
if self.num_simulations == 0:
print("No simulation data found in "+self.fname+'.')
return
else:
print(str(self.num_simulations)+" simulations found in "+self.fname+'.')
print()
print(" Simulation Name | Number ")
print("------------------------------------------|--------")
for i, name in enumerate(self.simulation_names):
# : is for padding. ^ means centre, > means right justified
# left justified is default
print("{:42}|{:^8}".format(name, i))
print()
return
def choose_sim(self):
if self.num_simulations == 0:
return
while True:
print("Please select a simulation number. To return to the main menu, type 'q'.")
sim_num = input()
if sim_num == 'q':
return 'q'
try:
sim_num = int(sim_num)
except ValueError:
print("Input should be an integer (whole number). Please try again.")
else:
if sim_num not in range(self.num_simulations):
print("Number not available. Please try again.")
else:
return sim_num
def print_sim(self, sim_number):
""" called when user requests information about a specific sim
"""
key = self.simulation_names[sim_number]
print("Getting data for simulation:", str(key), sep=' ')
print()
sim = self.main_dict[key]
print(" Category Name | Value ")
print("--------------------------|-------------------------------")
for i, entry in enumerate(self.header_list):
print(" "*26, "|", " "*31, sep='')
# avoid issues with floats/integers
v = str(sim[i])[:]
# determine how much space the entry will take up
if len(v) <= 31:
value = v
value_lines = 1
else:
value_lines = len(v) // 30 + 1
value = v[:30]
if entry != 'Source Directory':
value += '-'
print("{:26}|{:^31}".format(entry, value))
start = 30
end = 60
if value_lines > 1:
for m in range(1, value_lines):
# note, here is sort of weird
# python will not return index out of range error
# for a string len>30 with len%30 = 0
# instead it prints an extra line
# see note 4 from:
# https://docs.python.org/3/library/stdtypes.html
if m == value_lines-1:
if v[start:] != '':
print("{:26}|{:^31}".format('', v[start:]))
else:
value = v[start:end]
if entry != 'Source Directory':
value += '-'
print("{:26}|{:^31}".format('', value))
start = end
end += 30
print(" "*26, "|", " "*31, sep='')
print()
return
def write_data(self):
""" this function writes the contents of the main dictionary
self.main_dict to a file, called fname
the first instance of each simulation (the sim_name) is
used to sort the keys and hense the file is written
in alphabetical order according to sim_name
"""
with open(self.fname, 'w') as f:
# write header
f.write(self.header)
for key in sorted(self.main_dict):
sim = self.main_dict[key][:] # make copy of dict item
sim = str(sim).strip('[""]')+'\n' # rm sq brackets, add newline
sim = sim.replace('\\', '') # get rid of extra slashes
f.write(sim)
def add_sim(self):
""" gets information from user about a new simulation
the data will be added to the file
recently updated to use strings to call functions
works much better and looks cleaner
"""
print("Data will now be collected for the new simulation.")
print("To go back to the main menu, please type 'q'.")
print()
# get data for new sim
new_sim = []
for key in self.header_list:
function = self.category_functions[key]
entry = getattr(self, function)()
if entry == 'q':
return 'q'
else:
new_sim.append(entry)
# confirm addition:
print('', new_sim, '', "Add this simulation to "+self.fname+"?", sep='\n')
while True:
ans = input("Y/n ")
if ans in ('yes', 'YES', 'Yes', 'Y', 'y'):
print("Simulation will be added to "+self.fname+'.')
break
elif ans in ('no', 'NO', 'No', 'N', 'n', 'q'):
print("Simulation was not added to the database.")
return 'q'
else:
print("Invalid input. Try again.")
# add to database
self.num_simulations += 1 # add 1 to number of sims
self.main_dict[new_sim[0]] = new_sim # update dictionary
self.get_sim_names() # update names (sorted)
# re-write file
self.write_data()
def get_sim_name(self):
""" returns the name of the simulation or q for qutting/going
up a level in the nested loops. User cannot input a name
if it already exists in the data file.
"""
while True:
print("Enter the name of the new simulation.")
sim_name = input()
if sim_name == 'q':
return 'q'
elif sim_name in self.simulation_names:
print("That simulation name already exists. Please enter a new one.", '', sep='\n')
elif sim_name == 'DUMMY':
print("That simulation name is reserved by the system. Please enter a different name.")
else:
return sim_name
def get_sim_type(self):
""" the user provides information as to whether the simulation
is of a wild-type peptide or mutant peptide
"""
while True:
print("Is simulation of a wild-type (1) or mutant (2) peptide?")
p_type = input()
if p_type == 'q':
return 'q'
elif p_type != '1' and p_type != '2':
print("Invalid input. Please try again.")
elif p_type == '1':
p_type = 'WT'
return p_type
elif p_type == '2':
mutation = input("Enter the mutation (ex E480C): ")
p_type = 'Mu_'+mutation
return p_type
def get_protein_name(self):
""" user provides name of protein for peptide being simulated
"""
print("Provide the name of the protein being simulated (ex ProP):")
protein = input()
if protein == 'q':
return 'q'
else:
return protein
def get_protein_bacteria(self):
""" user provides name of bacteria that protein is from
"""
print("What bacteria name is your protein from (ex Ec, Xc)?")
bacteria = input()
if bacteria == 'q':
return 'q'
else:
return bacteria
def get_res_range(self):
""" user provides first residue being simulated, followed
by the last residue
first number has to be a non-zero, positive integer
second integer has to be higher/equal to than the first,
and a non-zero, positive integer
"""
while True:
print("For the protein being simulated, what is the first residue number?")
res_start = input()
if res_start == 'q':
return 'q'
# note that trying to convert the string to a integer directly
# will NOT work if the string is a floating point number!
try:
res_start = int(res_start)
except ValueError:
print("An integer value (whole number) should be given. Please try again.")
else:
if res_start <= 0:
print("Residue number should be greater than 0. Please try again.")
else:
break
while True:
print("Similarly, what is the last residue number?")
res_finish = input()
if res_finish == 'q':
return 'q'
try:
res_finish = int(res_finish)
except ValueError:
print("An integer value (whole number) should be given. Please try again.")
else:
if res_finish < res_start:
print("The last residue number should be greater than the first residue number. Please try again.")
else:
residue_range = str(res_start)+'-'+str(res_finish)
return residue_range
def get_salt_name(self):
""" user provides salt type for simulation
"""
print("Enter the salt type used in the simulation. Use \"N/A\" if no salt was used.")
salt_type = input()
if salt_type == 'q':
return 'q'
else:
return salt_type
def get_salt_conc(self):
""" user gives salt concentration in moles/litre (M)
input cannot include units
"""
while True:
print("Enter the concentration of salt used in moles/litre. Use \"N/A\" if no salt was used. Do not put units.")
salt_conc = input()
if salt_conc == 'q':
return 'q'
elif salt_conc == 'N/A':
return 'N/A'
try:
salt_conc = float(salt_conc)
except ValueError:
print("This value should be a number. Please try again.")
else:
if salt_conc <= 0:
print("This value should be greater than zero. Please try again.")
else:
return salt_conc
def get_comments(self):
""" user inputs comments about their simulation
"""
while True:
print("Extra comments can be put about the simulation here (i.e. coiled-coil, single helix, parallel, anti-parallel, etc.): ")
comments = input()
if comments == 'q':
return 'q'
else:
return comments
def get_sim_length(self):
""" user provides length of simulation completed in nanoseconds
"""
while True:
print("Please enter the number of nanoseconds completed so far for this simulation.")
ns = input()
if ns == 'q':
return 'q'
try:
ns = float(ns)
except ValueError:
print("Value should be a number. Please try again.")
else:
if ns < 0:
print("Value should be greater than, or equal to, zero. Please try again.")
else:
return ns
def get_sim_server(self):
""" user inputs where the simulation is currently running
"""
print("Please enter the name of the server where the simulation is currently running: ")
server = input()
if server == 'q':
return 'q'
else:
return server
def get_sim_dir(self):
""" user inputs where the data for their simulation is stored
"""
print("Please enter the directory path to where the simulation data is stored (i.e. the namd folder): ")
dir_path = input()
if dir_path == 'q':
return 'q'
else:
return dir_path
def get_update_choice(self):
""" user is prompted to chose an aspect to update about the simulation
"""
while True:
print()
print("Choose a category to update, or type 'q' to quit.")
for i, option in enumerate(self.header_list):
print('(', i, ') ', option, sep='')
print()
choice = input()
if choice == 'q':
return 'q'
elif choice not in [ str(x) for x in range(self.header_length)]:
print("Option not available. Please try again.")
else:
return int(choice)
def update_choice(self, sim_num, cat_num):
""" is called after user selects choice from get_update_choice
"""
# hold on to impt info
cat_name = self.header_list[cat_num]
old_entry_name = self.simulation_names[sim_num]
old_entry = self.main_dict[old_entry_name][cat_num]
print("Category", cat_name, 'will now be updated.')
function = self.category_functions[cat_name]
entry = getattr(self, function)()
if entry == 'q':
return 'q'
else:
# confirm change
print("Confirm data change:", old_entry,'to', entry)
while True:
ans = input("Y/n ")
if ans in ('yes', 'YES', 'Yes', 'Y', 'y'):
print("Simulation will be updated.")
break
elif ans in ('no', 'NO', 'No', 'N', 'n', 'q'):
print("Simulation was not updated.")
return 'q'
else:
print("Invalid input. Try again.")
# do entry addition
# changing the name might mean an order change for the sorted
# main_dict
if cat_num == 0:
print("Since a name change has been requested, the program will now update the file alphabetically.")
self.main_dict[old_entry_name][cat_num] = entry
self.main_dict[entry] = self.main_dict.pop(old_entry_name)
self.get_sim_names()
self.write_data()
return 'name_change'
else:
self.main_dict[old_entry_name][cat_num] = entry
self.write_data()
return
def update_sim(self, sim_num):
""" used to call other functions to make main function
cleaner
"""
while True:
self.print_sim(sim_num)
category = self.get_update_choice()
if category == 'q':
return 'q'
else:
update = sim_data.update_choice(sim_num, category)
if update == 'name_change':
return 'name_change'
if __name__ == "__main__":
sim_data = DataSheet()
while True:
print('', "Options: ", sep='\n')
print("(1) Make a new simulation record.")
print("(2) Update a current simulation record in "+sim_data.fname+'.')
print("(3) View details about a current simulation record.")
print("(4) Quit.", '', sep='\n')
choice = input()
print()
# quit
if choice in ('4', 'q'):
break
# print data for chosen simulation
elif choice == '3':
sim_data.print_sim_names()
sim_num = sim_data.choose_sim()
if sim_num != 'q':
sim_data.print_sim(sim_num)
# allow user to update simulation record with new data
elif choice == '2':
sim_data.print_sim_names()
sim_num = sim_data.choose_sim()
if sim_num != 'q':
sim_data.update_sim(sim_num)
# add new simulation to the database
elif choice == '1':
sim_data.add_sim()
# invalid input (choice not available)
else:
print("Invalid input. Try again.")