-
Notifications
You must be signed in to change notification settings - Fork 1
/
MpFileExtractorAndGeneratorHelper.py
1662 lines (1410 loc) · 97.3 KB
/
MpFileExtractorAndGeneratorHelper.py
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
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Python modules
import pickle
import re
import string
from os import listdir, mkdir, path
from shutil import copyfile
from StringIO import StringIO
from time import time, localtime, strftime
# Python extension modules (requires extension installations)
import numpy as np
import pandas as pd
from patsy.builtins import *
import statsmodels.api as sm
# Tool library modules
from MpParameterBoundConstraintHelper import MpParameterBoundConstraintHelper
from PopulationMatrixGenerator import PopulationMatrixGenerator
# Global method to join numpy arrays (simplifies join defined in config)
def join_arrays(array1, array2) :
return np.append(array1, array2, 1)
## MP (RAMAS Metapop) file extractor and generator helper
## * Extracts parameters from mp files into matrix structures via configured mappings
## * Creates a template of parameter placeholders for modified mp file generation
## * Generates modified mp files by substituting modified parameter values into the template
## * Generates a data frame file summarising the modified mp files
## * Generates a RAMAS Metapop batch file for running the modified mp files
## * Extracts results RAMAS Metapop result files
## * Generates a result summary for selected results
class MpFileExtractorAndGeneratorHelper :
# Initialise with the Metapop config
def __init__(self, mp_config) :
self.config = mp_config
# Maintain a copy of the parameter mapping from config
self.parameter_mapping = self.copy_parameter_mapping()
# Extracted parameters
self.extracted_parameters = []
# Parameter file links: { link_frame : [DataFrame], file_contents: {}, link_key, use_file_value : first/max }
self.parameter_file_links = {}
# Initialise parameters selected
self.parameters_selected = []
# Structure to store baseline file content and details
self.baseline_mp_file = { 'path' : '', 'directory' : '', 'name' : '', 'lines' : [] }
# Additional parameter values extracted from MP file
self.additional_parameter_values = {}
# Modified additional parameters to use when generating MP files
self.modified_additional_parameter_values = {}
# Additional parameters linked to baseline parameters (written to template)
self.link_to_additional_parameters = {}
# Data frame summarises generated MP files
self.data_frame = []
# RAMAS Metapop batch file for the generated MP files
self.mp_batch = []
# Create a MP parameter constraint helper
self.mp_constraint_helper = MpParameterBoundConstraintHelper(self.config)
# Compile a list of any parameter constraint violations marked with a * (for notes) in the data frame
self.parameter_constraint_violations = {}
# Initialise maximum MP file name width (for data frame formatting)
self.maximum_mp_file_name_width = 0
# Output template
self.output_template = ''
# Output directory
self.output_directory = { 'name' : '', 'directory' : '', 'path' : '' }
# Generation data: serialised to file for subsequent flexible cross-referencing with RAMAS Metapop outputs
self.generation_data = {}
# Results directory
self.results_directory = { 'name' : '', 'directory' : '', 'path' : '' }
# Loaded results from MP runs
self.loaded_results = {}
# Loaded result plots from MP runs
self.loaded_result_plots = {}
# Method resets stored helper variables associated with sampling file generation (not load)
def reset_sampling_generation(self) :
self.parameters_selected = []
self.data_frame = []
self.mp_batch = []
self.parameter_constraint_violations = {}
self.maximum_mp_file_name_width = 0
self.modified_additional_parameter_values = {}
self.output_template = ''
self.output_directory = { 'name' : '', 'directory' : '', 'path' : '' }
# Method resets stored helper variables associated with MP result loading
def reset_result_load(self) :
self.results_directory = { 'name' : '', 'directory' : '', 'path' : '' }
self.loaded_results = {}
self.loaded_result_plots = {}
# Is the baseline MP file loaded?
def isBaselineMpFileLoaded(self) :
if self.baseline_mp_file['path'] :
return True
else :
return False
# Have MP results been loaded successfully?
def mpResultsLoadedOk(self) :
if self.loaded_results and self.loaded_result_plots:
return True
else :
return False
# Is the result plot data loaded
def isResultPlotDataLoaded(self) :
if self.loaded_result_plots :
return True
else :
return False
# Get the baseline MP file name
def getBaselineMpFileName(self) :
return self.baseline_mp_file['name']
# Get the baseline MP file directory
def getBaselineMpFileDirectory(self) :
return self.baseline_mp_file['directory']
# Get parameter file links
def getParameterFileLinks(self) :
return self.parameter_file_links
# Get additional parameter value
def getAdditionalParameterValue(self, key) :
if self.additional_parameter_values.has_key(key) :
return self.additional_parameter_values[key]
else :
return None
# Get results plot data
def getResultsPlotData(self) :
return self.loaded_result_plots
# Get the output directory for the current generation
def getOutputDirectory(self) :
return self.output_directory
# Set the output directory for the current generation
def setOutputDirectory(self, output_directory_path) :
self.output_directory = self.splitPath(output_directory_path)
# Modify additional parameter values when generating MP files (template)
def modifyAdditionalParameterValues(self, modified_values) :
for key, value in modified_values.items() :
self.modified_additional_parameter_values[key] = value
# Method loads baseline MP file
def loadBaselineMpFile(self, file_path) :
# Split path into path, directory, and file name
self.baseline_mp_file = self.splitPath(file_path)
# Read file lines
f = open(self.baseline_mp_file['path'])
self.baseline_mp_file['lines'] = f.readlines()
f.close()
# Method splits path into a dictionary containing path, name (end), and (parent) directory
def splitPath(self, full_path) :
full_path = path.normpath(str(full_path))
path_dictionary = { 'path' : full_path }
split_path = path.split(full_path)
path_dictionary['directory'] = split_path[0]
path_dictionary['name'] = split_path[1]
return path_dictionary
# Method recursively creates a new directory path (eliminates duplicate existing subdirectories)
def createDirectoryPath(self, dir_path) :
root_dir_path = self.splitPath(dir_path)['directory']
new_dir_name = self.splitPath(dir_path)['name']
if not(path.exists(root_dir_path)) :
root_dir_path = self.createDirectoryPath(root_dir_path)
if new_dir_name == self.splitPath(root_dir_path)['name'] :
return root_dir_path
else :
new_path = path.join(root_dir_path, new_dir_name)
mkdir(new_path)
return new_path
# Create a copy of the parameter mapping from the config
def copy_parameter_mapping(self) :
return self.copy_dict_mapping(self.config.parameter_mapping)
# Copy dictionary mapping (recursive)
def copy_dict_mapping(self, dict_mapping) :
mapping_copy = {}
for key, item in dict_mapping.items() :
if type(item) == dict :
mapping_copy[key] = self.copy_dict_mapping(item)
else :
mapping_copy[key] = item
return mapping_copy
# Method extracts all configured parameters from a list of MP file contents
def extractParametersFromMpFileContents(self) :
# Resolve dynamic MP parameter mapping configuration
if self.parameter_mapping.has_key('dynamic_variables') :
self.resolveDynamicMpParameterMappingConfiguration(self.baseline_mp_file['lines'])
# Copy parameter mapping from sectioned parameters to their subsets
for parameter_key in self.config.parameters :
if self.parameter_mapping[parameter_key].has_key('subset_of') :
self.parameter_mapping[parameter_key].update(self.parameter_mapping[self.parameter_mapping[parameter_key]['subset_of']])
# Copy values into a matrix for each parameter:
parameter_values = {}
self.parameter_file_links = {}
self.extracted_parameters = []
for parameter_key in self.config.parameters :
conditions_ok = True
if self.parameter_mapping.has_key('conditions') :
if self.parameter_mapping['conditions'].has_key(parameter_key) :
if not self.parameter_mapping['conditions'][parameter_key]['satisfied'] :
conditions_ok = False
if conditions_ok :
if self.parameter_mapping[parameter_key].has_key('layers') : # file links not implemented for layers
for layer_index in range(self.parameter_mapping[parameter_key]['layers']) :
if layer_index == 0 :
value_matrix = np.array([self.extractMatrixFromFileLines(self.baseline_mp_file['lines'], self.parameter_mapping[parameter_key])])
else :
next_layer_mapping = self.parameter_mapping[parameter_key].copy()
next_layer_mapping['start_row'] += next_layer_mapping['next_layer']*layer_index
next_layer_values = self.extractMatrixFromFileLines(self.baseline_mp_file['lines'], next_layer_mapping)
value_matrix = np.append(value_matrix, [next_layer_values], axis=0)
else :
value_matrix = self.extractMatrixFromFileLines(self.baseline_mp_file['lines'], self.parameter_mapping[parameter_key])
if self.parameter_mapping[parameter_key].has_key('may_link_to_temporal_trend_files') :
if self.parameter_mapping[parameter_key]['may_link_to_temporal_trend_files'] :
link_frame = self.extractMatrixFromFileLines(self.baseline_mp_file['lines'], self.parameter_mapping[parameter_key], link_to_file=True)
if self.fileLinksExist(link_frame) :
self.parameter_file_links[parameter_key] = { 'file_contents' : {}, 'link_key' : parameter_key, 'link_frame' : link_frame, 'use_file_value' : '' }
if self.parameter_mapping[parameter_key].has_key('use_file_value') :
self.parameter_file_links[parameter_key]['use_file_value'] = self.parameter_mapping[parameter_key]['use_file_value']
if self.parameter_mapping[parameter_key].has_key('subset_of') and self.parameter_mapping[parameter_key].has_key('subset_mask') :
if len(value_matrix.shape) == 3 : # layered
subset_mask_matrix = np.ones_like(value_matrix[0])
else :
subset_mask_matrix = np.ones_like(value_matrix)
if self.parameter_mapping.has_key('subset_masks') and self.parameter_mapping['subset_masks'].has_key(parameter_key) :
subset_mask_matrix = self.extractMatrixFromFileLines(self.baseline_mp_file['lines'], self.parameter_mapping['subset_masks'][parameter_key])
if self.parameter_mapping['subset_masks'][parameter_key].has_key('inverse') and self.parameter_mapping['subset_masks'][parameter_key]['inverse'] :
subset_mask_matrix = subset_mask_matrix*-1+1
subset_mask = self.resolveSubsetMask(parameter_key, subset_mask_matrix)
if len(value_matrix.shape) == 3 : # layered
mask = subset_mask
subset_mask = np.ones_like(value_matrix)
subset_mask[:] = mask
value_matrix *= subset_mask
if self.parameter_mapping[parameter_key].has_key('mask_values') :
value_mask = np.ones_like(value_matrix)
for mask_value in self.parameter_mapping[parameter_key]['mask_values'] :
value_mask *= (value_matrix == mask_value)
if (value_matrix.flatten() != 0).max() : # has non-zero values including potential nan's from linked file names
if self.parameter_mapping[parameter_key].has_key('subset_of') and self.parameter_mapping[parameter_key].has_key('subset_mask') :
parameter_values[parameter_key] = np.ma.masked_array(value_matrix, mask=subset_mask*-1+1)
elif self.parameter_mapping[parameter_key].has_key('mask_values') :
parameter_values[parameter_key] = np.ma.masked_array(value_matrix, mask=value_mask)
else :
parameter_values[parameter_key] = value_matrix
self.extracted_parameters.append(parameter_key)
# Extract any additional parameters that may be required
self.additional_parameter_values = {}
if self.parameter_mapping.has_key('additional') :
for key, mapping in self.parameter_mapping['additional'].items() :
# Extract
link_to_trend_file = False
if mapping.has_key('line') :
line_number = eval(str(mapping['line']))
line = self.baseline_mp_file['lines'][line_number-1]
self.additional_parameter_values[key] = eval(str(mapping['value']))
elif mapping.has_key('value') :
self.additional_parameter_values[key] = mapping['value']
else :
if mapping.has_key('may_link_to_temporal_trend_files') :
link_to_trend_file = mapping['may_link_to_temporal_trend_files']
value = self.extractMatrixFromFileLines(self.baseline_mp_file['lines'], mapping, link_to_file=link_to_trend_file)
if not link_to_trend_file :
if np.size(value) == 1 :
value = value.flatten().tolist().pop()
self.additional_parameter_values[key] = value
# Link to parameters where required
if mapping.has_key('link_to_parameter') :
self.link_to_additional_parameters[mapping['link_to_parameter']] = key
if link_to_trend_file and (mapping['link_to_parameter'] in self.extracted_parameters) :
if not self.parameter_file_links.has_key(mapping['link_to_parameter']) :
self.parameter_file_links[mapping['link_to_parameter']] = { 'link_frame' : self.additional_parameter_values[key], 'file_contents' : {}, 'link_key' : key, 'use_file_value' : '' }
if mapping.has_key('use_file_value') :
self.parameter_file_links[mapping['link_to_parameter']]['use_file_value'] = mapping['use_file_value']
return parameter_values
# Method resolves subset mask using parameter mapping
def resolveSubsetMask(self, parameter_key, subset_mask_matrix) :
subset_mask = np.zeros_like(subset_mask_matrix)
mapping = self.parameter_mapping[parameter_key]
if mapping['subset_mask'].has_key('whole_matrix') :
subset_mask = self.resolveSubmatrixMask(subset_mask, mapping['subset_mask']['whole_matrix'])
elif mapping['subset_mask'].has_key('quadrants') and mapping['subset_mask']['quadrants'].has_key('divide_at') :
divider = mapping['subset_mask']['quadrants']['divide_at']
if mapping['subset_mask']['quadrants'].has_key('upper_left') :
subset_mask[:divider,:divider] = self.resolveSubmatrixMask(subset_mask[:divider,:divider], mapping['subset_mask']['quadrants']['upper_left'])
if mapping['subset_mask']['quadrants'].has_key('lower_left') :
subset_mask[divider:,:divider] = self.resolveSubmatrixMask(subset_mask[divider:,:divider], mapping['subset_mask']['quadrants']['lower_left'])
if mapping['subset_mask']['quadrants'].has_key('upper_right') :
subset_mask[:divider,divider:] = self.resolveSubmatrixMask(subset_mask[:divider,divider:], mapping['subset_mask']['quadrants']['upper_right'])
if mapping['subset_mask']['quadrants'].has_key('lower_right') :
subset_mask[divider:,divider:] = self.resolveSubmatrixMask(subset_mask[divider:,divider:], mapping['subset_mask']['quadrants']['lower_right'])
return subset_mask*subset_mask_matrix
# Method resolves a submatrix mask
def resolveSubmatrixMask(self, submatrix_mask, submatrix_mask_mapping) :
# Partition mask
if submatrix_mask_mapping.has_key('partition') :
if submatrix_mask_mapping.has_key('include_diagonal') and submatrix_mask_mapping['include_diagonal'] :
submatrix_mask += np.eye(N=submatrix_mask.shape[0], M=submatrix_mask.shape[1], k=0)
for i in np.arange(1, max(submatrix_mask.shape)) :
if submatrix_mask_mapping['partition'] is 'diagonal_upper_right' :
submatrix_mask += np.eye(N=submatrix_mask.shape[0], M=submatrix_mask.shape[1], k=i)
elif submatrix_mask_mapping['partition'] is 'diagonal_lower_left' :
submatrix_mask += np.eye(N=submatrix_mask.shape[0], M=submatrix_mask.shape[1], k=-1*i)
# Row inclusion mask
if submatrix_mask_mapping.has_key('rows') :
if submatrix_mask_mapping['rows'] is 'first' :
submatrix_mask[0,:] = 1
elif submatrix_mask_mapping['rows'] is 'all' :
submatrix_mask[:,:] = 1
elif submatrix_mask_mapping['rows'] is 'below_first' :
submatrix_mask[1:,:] = 1
return submatrix_mask
# Method checks to see if file links exist
def fileLinksExist(self, link_frame) :
file_links_found = False
for item in link_frame.get_values().flatten() :
file_path = path.join(self.baseline_mp_file['directory'], str(item))
if item and path.exists(file_path) :
file_links_found = True
return file_links_found
# Method loads files linked to parameters
def loadFilesLinkedToParameters(self) :
for parameter_key, file_links in self.parameter_file_links.items() :
for item in file_links['link_frame'].get_values().flatten() :
file_path = path.join(self.baseline_mp_file['directory'], str(item))
if item and path.exists(file_path) :
file_links['file_contents'][item] = pd.read_csv(file_path, header=None)
return self.parameter_file_links
# Method resolves dynamic MP parameter mapping configuration by extracting details from the MP file
def resolveDynamicMpParameterMappingConfiguration(self, file_lines) :
# Reset the copy of the parameter mapping
self.parameter_mapping = self.copy_parameter_mapping()
# Ignore comment lines if appropriate
search_from_row = 1
if self.parameter_mapping.has_key('search_for_dynamic_variables_from_row') :
search_from_row = self.parameter_mapping['search_for_dynamic_variables_from_row']
# Extract the dynamic variables from the file lines
dynamic_variable_values = {}
for name, dynamic_variable_map in self.parameter_mapping['dynamic_variables'].items() :
# Extraction using regular expression patterns for line number or value extraction
if dynamic_variable_map.has_key('pattern') :
found = self.findPatternInFileLines(dynamic_variable_map['pattern'], file_lines, search_from_row)
extract = dynamic_variable_map['value']
if extract.find('line') >= 0 :
line = found['line']
dynamic_variable_values[name] = eval(extract)
else : # formula containing match
match = found['match']
dynamic_variable_values[name] = eval(extract)
# Extraction using matrix extraction (assumes single value 1x1 matrix)
else :
dynamic_variable_values[name] = int(self.extractMatrixFromFileLines(file_lines, dynamic_variable_map)[0,0])
# Create the dynamic variables with their given values
for name, value in dynamic_variable_values.items() :
exec(name + ' = value')
# Resolve options if any
if self.parameter_mapping.has_key('options') :
for parameter_key, options in self.parameter_mapping['options'].items() :
for option_key, option in options.items() :
if option.has_key('value') :
line_number = eval(str(option['line']))
line = file_lines[line_number-1]
options[option_key] = eval(str(option['value']))
elif option.has_key('values') :
from_line = eval(str(option['from_line']))
lines = eval(str(option['lines']))
options[option_key] = []
for i in range(lines) :
line = file_lines[from_line-1+i]
options[option_key].append(eval(str(option['values'])))
elif option.has_key('value_list') :
line_number = eval(str(option['line']))
line = file_lines[line_number-1]
options[option_key] = eval(str(option['value_list']))
# Resolve alternatives from options if any
if self.parameter_mapping.has_key('alternatives') :
for key, alternatives in self.parameter_mapping['alternatives'].items() :
# Resolve and exchange alternative from options for parameters
if self.parameter_mapping.has_key(key) and self.parameter_mapping[key].has_key('choose_alternative') :
chosen_option = self.parameter_mapping['options'][key].get(alternatives['option'])
if type(chosen_option) is list :
for option_value in chosen_option :
if alternatives.has_key(option_value) :
self.parameter_mapping[key] = alternatives[option_value]
else :
if alternatives[chosen_option].has_key('option') : # has nested options
nested_option = self.parameter_mapping['options'][key].get(alternatives[chosen_option]['option'])
self.parameter_mapping[key] = alternatives[chosen_option][nested_option]
else :
self.parameter_mapping[key] = alternatives[chosen_option]
if self.parameter_mapping[key].has_key('calculate_matrix') :
self.parameter_mapping[key]['calculate_matrix']['parameters'] = self.parameter_mapping['options'][key]
# Resolve and exchange alternative from options for additional parameters
elif self.parameter_mapping.has_key('additional') and self.parameter_mapping['additional'].has_key(key) and self.parameter_mapping['additional'][key].has_key('choose_alternative') :
chosen_option = self.parameter_mapping['options'][key].get(alternatives['option'])
if type(chosen_option) is list :
for option_value in chosen_option :
if alternatives.has_key(option_value) :
self.parameter_mapping['additional'][key] = alternatives[option_value]
else :
self.parameter_mapping['additional'][key] = alternatives[chosen_option]
# Resolve and exchange alternative from options for parameter conditions
elif key == 'conditions' :
for parameter_key, condition_alternatives in alternatives.items() :
if self.parameter_mapping['conditions'][parameter_key].has_key('choose_alternative') :
if condition_alternatives.has_key('precondition') and condition_alternatives.has_key('postcondition') :
precond_alt_key = self.parameter_mapping['options'][parameter_key].get(condition_alternatives['precondition']['option'])
precondition = condition_alternatives['precondition'][precond_alt_key]
if precondition.has_key('option') :
precondition['values'] = self.parameter_mapping['options'][parameter_key][precondition['option']]
postcond_alt_key = self.parameter_mapping['options'][parameter_key].get(condition_alternatives['postcondition']['option'])
postconditions = { False : condition_alternatives['postcondition'][False][postcond_alt_key], True : condition_alternatives['postcondition'][True][postcond_alt_key] }
if postconditions[False].has_key('option') :
postconditions[False]['values'] = self.parameter_mapping['options'][parameter_key][postconditions[False]['option']]
if postconditions[True].has_key('option') :
postconditions[True]['values'] = self.parameter_mapping['options'][parameter_key][postconditions[False]['option']]
self.parameter_mapping['conditions'][parameter_key] = { 'precondition' : precondition, 'postconditions' : postconditions, 'satisfied' : False }
elif condition_alternatives.has_key('option') :
alt_key = self.parameter_mapping['options'][parameter_key].get(condition_alternatives['option'])
self.parameter_mapping['conditions'][parameter_key] = condition_alternatives[alt_key]
if self.parameter_mapping['conditions'][parameter_key].has_key('option') :
option_key = self.parameter_mapping['conditions'][parameter_key].get('option')
self.parameter_mapping['conditions'][parameter_key]['values'] = self.parameter_mapping['options'][parameter_key][option_key]
# Resolve conditions if any
if self.parameter_mapping.has_key('conditions') :
for parameter_key, condition in self.parameter_mapping['conditions'].items() :
if condition.has_key('line') :
if re.findall(condition['should_match'], file_lines[eval(condition['line'])-1]) :
condition['satisfied'] = True
else :
condition['satisfied'] = False
elif condition.has_key('values') :
match_found = False
negation_found = False
for value in condition['values'] :
match_found = (match_found or (value in condition['match_any']))
if condition.has_key('match_none') :
negation_found = (negation_found or (value in condition['match_none']))
condition['satisfied'] = (match_found and not negation_found)
elif condition.has_key('precondition') and condition.has_key('postconditions') :
precond_match_found = False
precond_negation_found = False
for precond_value in condition['precondition']['values'] :
precond_match_found = (precond_match_found or (precond_value in condition['precondition']['match_any']))
if condition['precondition'].has_key('match_none') :
precond_negation_found = (precond_negation_found or (precond_value in condition['precondition']['match_none']))
condition['precondition']['satisfied'] = (precond_match_found and not precond_negation_found)
postcondition = condition['postconditions'][condition['precondition']['satisfied']]
postcond_match_found = False
postcond_negation_found = False
for postcond_value in postcondition['values'] :
postcond_match_found = (postcond_match_found or (postcond_value in postcondition['match_any']))
if postcondition.has_key('match_none') :
postcond_negation_found = (postcond_negation_found or (postcond_value in postcondition['match_none']))
condition['satisfied'] = (postcond_match_found and not postcond_negation_found)
elif condition.has_key('condition') :
condition['satisfied'] = eval(condition['condition'])
# Include sectioned parameter mappings
parameters = self.config.parameters[:] # copy
for parameter_key in self.config.parameters :
if self.parameter_mapping[parameter_key].has_key('subset_of') :
sectioned_parameter = self.parameter_mapping[parameter_key]['subset_of']
if sectioned_parameter not in parameters :
parameters.append(sectioned_parameter)
# Insert the dynamic variable values into the parameter MP mapping configuration
for parameter_key in parameters :
for key, value in self.parameter_mapping[parameter_key].items() :
if key in ['number_rows', 'number_columns', 'start_row', 'start_column', 'layers', 'next_layer'] and type(value) == str :
self.parameter_mapping[parameter_key][key] = eval(value)
elif key is 'subset_mask' and value.has_key('quadrants') and value['quadrants'].has_key('divide_at') :
self.parameter_mapping[parameter_key]['subset_mask']['quadrants']['divide_at'] = eval(value['quadrants']['divide_at'])
# Insert the dynamic variable values into the parameter subset mask MP mapping configuration
if self.parameter_mapping.has_key('subset_masks') :
for parameter_key in self.parameter_mapping['subset_masks'].keys() :
for key, value in self.parameter_mapping['subset_masks'][parameter_key].items() :
if key in ['number_rows', 'number_columns', 'start_row', 'start_column'] and type(value) == str :
self.parameter_mapping['subset_masks'][parameter_key][key] = eval(value)
# Insert the dynamic variable values into any additional parameter MP mapping configuration
if self.parameter_mapping.has_key('additional') :
for parameter_key in self.parameter_mapping['additional'].keys() :
if not self.parameter_mapping['additional'][parameter_key].has_key('line') :
for key, value in self.parameter_mapping['additional'][parameter_key].items() :
if key in ['number_rows', 'number_columns', 'start_row', 'start_column', 'value'] and type(value) == str :
self.parameter_mapping['additional'][parameter_key][key] = eval(value)
# Insert the dynamic variable values into the omit_results_from_mp_template MP mapping configuration
if self.parameter_mapping.has_key('omit_results_from_mp_template') :
for key, value in self.parameter_mapping['omit_results_from_mp_template'].items() :
if key in ['from_line', 'to_line'] and type(value) == str :
self.parameter_mapping['omit_results_from_mp_template'][key] = eval(value)
# Method extracts a matrix of values from file lines given a specified mapping
def extractMatrixFromFileLines(self, file_lines, mapping, link_to_file=False) :
# Compile mapping specifications
extraction = 'specified'
delimiter = mapping['delimiter']
from_row = mapping['start_row']
if mapping.has_key('number_rows') and mapping.has_key('start_column') and mapping.has_key('number_columns') :
extract = 'matrix'
number_rows = mapping['number_rows']
to_row = from_row + number_rows - 1
from_column = mapping['start_column']
number_columns = mapping['number_columns']
to_column = from_column + number_columns - 1
elif mapping.has_key('until_value_condition') and mapping.has_key('column') : # single column
extraction = 'conditional'
until_value_condition = mapping['until_value_condition']
column = mapping['column']
if mapping.has_key('under_diagonal_only') and extraction is 'specified' : # no link to file considered
# Initialise as a zero matrix
matrix = np.zeros(tuple([number_rows, number_columns]))
# Extract the values below the main diagonal one row at a time
for columns, row in enumerate(range(from_row, to_row)) :
column_indexes = tuple(range(from_column-1, from_column+columns))
value_array = np.genfromtxt(StringIO(file_lines[row]), delimiter=delimiter, usecols=column_indexes, autostrip=True)
matrix[row-from_row+1, 0:columns+1] = value_array
return matrix
elif extraction is 'specified' :
lines_string = string.join(file_lines[from_row-1:to_row], sep='')
if link_to_file :
if lines_string.strip() :
# Extract as a data frame
return pd.read_csv(StringIO(lines_string), header=None, delimiter=delimiter, na_filter=False, usecols=range(from_column-1, to_column))
else :
return pd.DataFrame([])
else :
# Extract the values as a full matrix
column_indexes = tuple(range(from_column-1, to_column))
value_array = np.genfromtxt(StringIO(lines_string), delimiter=delimiter, usecols=column_indexes, autostrip=True)
# Correctly shape the array to a matrix (necessary when single row or column)
return np.reshape(value_array, (number_rows, number_columns))
elif extraction is 'conditional' :
# Iterate through file lines until condition satisfied or blank line
line = file_lines[from_row-1]
to_row = from_row
if line.strip() :
value = np.genfromtxt(StringIO(line), delimiter=delimiter, usecols=tuple(range(column-1, column)), autostrip=True)
while line.strip() and len(file_lines) >= (to_row + 1) and not eval(str(value)+until_value_condition) :
to_row += 1
line = file_lines[to_row-1]
if line.strip() :
value = np.genfromtxt(StringIO(line), delimiter=delimiter, usecols=tuple(range(column-1, column)), autostrip=True)
# Return column down to row that satisfies condition or until blank line
lines_string = string.join(file_lines[from_row-1:to_row], sep='')
value_array = np.genfromtxt(StringIO(lines_string), delimiter=delimiter, usecols=tuple(range(column-1, column)), autostrip=True)
# Correctly shape the array to a matrix (necessary when single row or column)
return np.reshape(value_array, (to_row-from_row+1, 1))
# Method constructs matrices defined as functions in MP file
def constructFunctionDefinedMatrices(self) :
calculated_parameter_values = {}
# Collect any matrix calculation details from resolved mapping
matrix_calculation_details = {}
for parameter_key in self.extracted_parameters :
if self.parameter_mapping[parameter_key].has_key('calculate_matrix') :
matrix_calculation_details[parameter_key] = self.parameter_mapping[parameter_key]['calculate_matrix']
# Calculate matrices from any details found
if matrix_calculation_details :
population_matrix_generator = PopulationMatrixGenerator(self.additional_parameter_values['population_coordinates'])
for parameter_key, details in matrix_calculation_details.items() :
matrix_type = details.get('type')
if matrix_type == 'population' :
calculated_parameter_values[parameter_key] = population_matrix_generator.calculateMatrix(details)
return calculated_parameter_values
# Method creates a generation template for a selection of parameters
def createGenerationTemplate(self, parameter_list=[], write_template='') :
# Set the selected parameter list
self.parameters_selected = parameter_list
# Create an output MP template
template_lines = self.baseline_mp_file['lines'][:] # A copy
# Omit result lines if required
if self.parameter_mapping.has_key('omit_results_from_mp_template') and self.parameter_mapping['omit_results_from_mp_template']['omit'] :
if type(self.parameter_mapping['omit_results_from_mp_template']['from_line']) is int and type(self.parameter_mapping['omit_results_from_mp_template']['to_line']) is int :
if self.parameter_mapping['omit_results_from_mp_template']['from_line'] <= self.parameter_mapping['omit_results_from_mp_template']['to_line'] :
template_lines = self.baseline_mp_file['lines'][:self.parameter_mapping['omit_results_from_mp_template']['from_line']-1]
template_lines.append(self.baseline_mp_file['lines'][self.parameter_mapping['omit_results_from_mp_template']['to_line']])
# Correct any file paths
if self.parameter_mapping.has_key('additional') :
for key, mapping in self.parameter_mapping['additional'].items() :
if mapping.has_key('file_with_full_path') :
if self.additional_parameter_values[key] :
if not path.exists(self.additional_parameter_values[key]) :
local_path = path.join(self.baseline_mp_file['directory'], self.splitPath(self.additional_parameter_values[key])['name'])
if path.exists(local_path) :
self.modifyAdditionalParameterValues({ key : local_path })
# Modify any additional parameter values (implemented for string and single integer values only)
for key, value in self.modified_additional_parameter_values.items() :
if self.parameter_mapping['additional'].has_key(key) :
parameter_mapping = self.parameter_mapping['additional'][key]
if parameter_mapping.has_key('start_row') :
template_lines[parameter_mapping['start_row']-1] = re.sub('\d+', str(value), template_lines[parameter_mapping['start_row']-1])
elif parameter_mapping.has_key('line') :
template_lines[parameter_mapping['line']-1] = template_lines[parameter_mapping['line']-1].replace(str(self.additional_parameter_values[key]), str(value))
# Ensure any existing $ characters are made safe for templating and remove new lines
for index, line in enumerate(template_lines) :
if '$' in line :
template_lines[index] = template_lines[index].replace('$','$$')
# Initialise template parameters
template_parameters = self.parameters_selected[:] # A copy
# Substitute the parent parameter when a parameter is a subset of another
for parameter_key in self.parameters_selected :
if self.parameter_mapping[parameter_key].has_key('subset_of') :
template_parameters.remove(parameter_key)
if not template_parameters.count(self.parameter_mapping[parameter_key]['subset_of']) :
template_parameters.append(self.parameter_mapping[parameter_key]['subset_of'])
# Add linked additional parameters
for parameter_key in self.parameters_selected :
if self.link_to_additional_parameters.has_key(parameter_key) :
template_parameters.append(self.link_to_additional_parameters[parameter_key])
# Write MP template entries for each parameter
for parameter_key in template_parameters :
# Get mapping for parameter
if self.parameter_mapping['additional'].has_key(parameter_key) :
mapping = self.parameter_mapping['additional'][parameter_key]
else :
mapping = self.parameter_mapping[parameter_key]
# Determine number of layers
if mapping.has_key('layers') :
layers = mapping['layers']
else :
layers = 1
# Build template entries for non-layered and layered parameters
for layer_index in range(layers) :
if mapping.has_key('layers') :
from_row = mapping['start_row'] + mapping['next_layer']*layer_index
else :
from_row = mapping['start_row']
number_rows = mapping['number_rows']
to_row = from_row + number_rows - 1
from_column = mapping['start_column']
number_columns = mapping['number_columns']
to_column = from_column + number_columns - 1
delimiter = mapping['delimiter']
# Pattern (regular expression) for line endings to preserve during template building
regex_for_endings = '[ \t\r\f\v]+$|\s*#.*$'
# Replace parameter row values with indexed template fields
for index, row in enumerate(range(from_row-1, to_row)) :
# Preserve and remove line ending
line_ending = '\n'
ending_list = re.findall(regex_for_endings, template_lines[row])
if ending_list :
line_ending = ending_list.pop() + '\n'
template_lines[row] = string.replace(template_lines[row], line_ending, '')
# Add template field
if mapping.has_key('layers') :
template_key = string.replace(parameter_key,' ','_') + '_' + str(layer_index) + '_' + str(index)
else :
template_key = string.replace(parameter_key,' ','_') + '_' + str(index)
split_row = template_lines[row].split(delimiter)
if mapping.has_key('under_diagonal_only') :
if index :
split_row[from_column-1:from_column+index-1] = '$'
split_row[from_column-1] = '${' + template_key + '}'
else :
split_row[from_column-1:to_column] = '$'
split_row[from_column-1] = '${' + template_key + '}'
template_lines[row] = string.join(split_row,delimiter)
# Re-attach the line ending
template_lines[row] += line_ending
# Join the lines of the output MP template
self.output_template = string.join(template_lines, sep='')
# For testing: Examine the MP template
if write_template :
f = open(self.output_directory['path'] + '\\' + write_template, 'w')
f.write(self.output_template)
f.close()
# Method generates an MP file by substituting modified parameter values into the output template
def generateModifiedMpFile(self, modified_parameter_values, file_number) :
# Copy modified parameter values
modified_parameter_values = self.copy_dict_mapping(modified_parameter_values)
# Initialise template parameters
template_parameters = self.parameters_selected[:] # A copy
# Substitute the parent parameter when a parameter is a subset of another and merge modified values into parent
for parameter_key in self.parameters_selected :
if self.parameter_mapping[parameter_key].has_key('subset_of') :
parent_key = self.parameter_mapping[parameter_key]['subset_of']
template_parameters.remove(parameter_key)
if not template_parameters.count(parent_key) :
template_parameters.append(parent_key)
modified_parameter_values[parent_key] = modified_parameter_values[parameter_key].data # assumed masked numpy array
else :
modified_parameter_values[parent_key] += modified_parameter_values[parameter_key].data # assumed masked numpy array
# Generate MP template substitution values for modified parameters
template_substitution_values = {}
for parameter_key in template_parameters :
mapping = self.parameter_mapping[parameter_key]
number_rows = mapping['number_rows']
number_columns = mapping['number_columns']
delimiter = mapping['delimiter']
mp_format = self.config.parameter_output_format[parameter_key]['mp_format']
# Determine number of layers
if mapping.has_key('layers') :
layers = mapping['layers']
else :
layers = 1
# Build template substitution values for non-layered and layered parameters
for layer_index in range(layers) :
# Single layer of modified parameter values
if mapping.has_key('layers') :
single_layer_values = modified_parameter_values[parameter_key][layer_index, :]
else :
single_layer_values = modified_parameter_values[parameter_key]
# Generate a delimited string of modified values for each row or line
for index in range(0, number_rows) :
# Extract the modified row values and convert them to a single row matrix
modified_value_matrix = np.array([])
if mapping.has_key('under_diagonal_only') :
if index :
modified_value_matrix = np.reshape(single_layer_values[index,0:index], (1, index))
else :
modified_value_matrix = np.reshape(single_layer_values[index,:], (1, number_columns))
# Generate the delimited modified value string for template substitution
if modified_value_matrix.size :
# Write modified matrix to string
string_buffer = StringIO()
np.savetxt(string_buffer, modified_value_matrix, fmt=mp_format, delimiter=delimiter, newline='')
substitution_string = string_buffer.getvalue()
# Substitute indexed linked file names (implemented as single column only)
if self.parameter_file_links.has_key(parameter_key) :
link_frame_entry = self.parameter_file_links[parameter_key]['link_frame'].iat[index, 0]
if self.parameter_file_links[parameter_key]['file_contents'].has_key(link_frame_entry) :
link_file_ext = string.split(link_frame_entry, '.').pop()
link_frame_entry = string.replace(link_frame_entry, ('.' + link_file_ext), (self.config.file_generation_numbering_format % file_number + '.' + link_file_ext))
if self.parameter_file_links[parameter_key]['link_key'] == parameter_key :
substitution_string = link_frame_entry
if self.parameter_file_links[parameter_key]['link_key'] != parameter_key :
template_key = string.replace(self.parameter_file_links[parameter_key]['link_key'],' ','_') + '_' + str(index)
template_substitution_values[template_key] = link_frame_entry
# Set template substitutions for parameter
if mapping.has_key('layers') :
template_key = string.replace(parameter_key,' ','_') + '_' + str(layer_index) + '_' + str(index)
else :
template_key = string.replace(parameter_key,' ','_') + '_' + str(index)
template_substitution_values[template_key] = substitution_string
# Substitute modified values into the output MP template
template = string.Template(self.output_template)
modified_mp_string = template.substitute(template_substitution_values)
# Construct output file name from baseline file
file_ext = string.split(self.baseline_mp_file['name'], '.').pop()
modified_file_name = self.baseline_mp_file['name'].replace(('.' + file_ext), ('_modified' + self.config.file_generation_numbering_format % file_number + '.' + file_ext))
#modified_file_name = string.split(self.baseline_mp_file['name'], '.')[0] + '_modified' + self.config.file_generation_numbering_format % file_number + '.mp'
if string.find(modified_file_name, '_baseline') > -1 :
modified_file_name = modified_file_name.replace('_baseline', '')
# Write modified output file
f = open(self.output_directory['path'] + '\\' + modified_file_name, 'w')
f.write(modified_mp_string)
f.close()
# Set maximum MP file name width
if self.maximum_mp_file_name_width < len(modified_file_name) :
self.maximum_mp_file_name_width = len(modified_file_name)
return modified_file_name
# Method generates the modified linked files
def generateModifiedLinkedFiles(self, modified_linked_file_contents, file_number) :
filenames = []
for parameter_key, file_links in modified_linked_file_contents.items() :
if parameter_key in self.parameters_selected :
for filename, contents in file_links.items() :
file_ext = string.split(filename, '.').pop()
filename = filename.replace(('.' + file_ext), (self.config.file_generation_numbering_format % file_number + '.' + file_ext))
output_file_path = path.join(self.output_directory['path'], filename)
if not self.config.parameter_data_types.has_key(parameter_key) : # float
pd.DataFrame(contents).to_csv(output_file_path, header=False, index=False, float_format=self.config.parameter_output_format[parameter_key]['mp_format'])
else : # assume integer
pd.DataFrame(contents).to_csv(output_file_path, header=False, index=False)
filenames.append(filename)
return filenames
# Method copies unmodified linked files to the output directory
def copyUnmodifiedLinkedFiles(self) :
filenames = []
# Copy linked files for parameters that were not selected
for parameter_key, file_links in self.parameter_file_links.items() :
if parameter_key not in self.parameters_selected :
for filename in file_links['file_contents'].keys() :
if path.exists(path.join(self.baseline_mp_file['directory'], filename)) :
copyfile(path.join(self.baseline_mp_file['directory'], filename), path.join(self.output_directory['path'], filename))
filenames.append(filename)
# Copy other linked files from additional parameters
for key, value_frame in self.additional_parameter_values.items() :
if self.parameter_mapping['additional'][key].has_key('copy_files') :
if self.parameter_mapping['additional'][key]['copy_files'] :
for item in value_frame.get_values().flatten() :
source_file_path = path.join(self.baseline_mp_file['directory'], str(item))
if item and path.exists(source_file_path) :
copyfile(source_file_path, path.join(self.output_directory['path'], item))
filenames.append(item)
return filenames
# Method generates a time-stamped directory within the current output directory
def generateTimestampedOutputDirectory(self) :
# Create directory name from date and time
timestamped_dir_name = strftime("%d%b%Y_%I%M%p_%S", localtime()) + '.' + string.split('%.3f' % time(), '.')[1] + 's'
# Create the directory within the current output directory
new_ouput_path = self.createDirectoryPath(path.join(self.output_directory['path'], timestamped_dir_name))
self.setOutputDirectory(new_ouput_path)
# Add timestamped directory name to output directory structure
self.output_directory['timestamped'] = timestamped_dir_name
return self.output_directory
# Method generates a data frame entry for the corresponding generated MP file
def generateDataFrameEntry(self, mp_file_name, modified_parameter_values, multipliers) :
# Generated MP file name
file_heading = self.config.data_frame_mp_file_heading
file_heading_width = max(self.maximum_mp_file_name_width, len(file_heading))
padding = file_heading_width - len(mp_file_name)
frame_line = mp_file_name + (('%' + str(padding) + 's') % '')
# Data frame entry details for generation data collection
entry_details = {}
# Selected parameter values or percentage change (when multiple values)
for parameter_key in self.parameters_selected :
# Data field appendage default to space so marker for notes stands out
data_field_appendage = ' ' * len(self.config.data_frame_notes_marker)
# Extract data only from masked values
if type(modified_parameter_values[parameter_key]) is np.ma.MaskedArray :
modified_parameter_values[parameter_key] = modified_parameter_values[parameter_key].data
# Determine if the parameter value(s) violate any constraints
constraint_violations = self.mp_constraint_helper.checkParameterValuesAgainstConstraints(parameter_key, modified_parameter_values[parameter_key])
if constraint_violations : # append violations to maintain a unique set
if self.parameter_constraint_violations.has_key(parameter_key) :
for new_violation in constraint_violations :
contained_within_existing = False
for existing_violation in self.parameter_constraint_violations[parameter_key] :
if new_violation['constraint_type'] == existing_violation['constraint_type'] and new_violation['violation'] == existing_violation['violation'] :
contained_within_existing = True
if not(contained_within_existing) :
self.parameter_constraint_violations.has_key(parameter_key).append(new_violation)
else :
self.parameter_constraint_violations[parameter_key] = constraint_violations
data_field_appendage = self.config.data_frame_notes_marker
# Add data frame entry details for parameter
entry_details[parameter_key] = { 'constraint_violations' : constraint_violations }
self.modified_parameter_values = modified_parameter_values # TEST
# Determine if value sums are involved
sum_values = False
if self.config.parameter_data_frame.has_key(parameter_key) :
sum_values = (self.config.parameter_data_frame[parameter_key] == 'sum_values')
# Substitute any linked file values when not summed
if self.parameter_file_links.has_key(parameter_key) and not sum_values :
values = []
for i, value in enumerate(modified_parameter_values[parameter_key]) :
link_frame_entry = self.parameter_file_links[parameter_key]['link_frame'].iat[i, 0]
if self.parameter_file_links[parameter_key]['file_contents'].has_key(link_frame_entry) :
file_contents = self.parameter_file_links[parameter_key]['file_contents'][link_frame_entry]
modified_file_values = file_contents.get_values()*multipliers[parameter_key]
values.extend(modified_file_values.flatten().tolist())
else :
values.extend(value.flatten().tolist())
modified_parameter_values[parameter_key] = np.array(values)
# Determine if multiple values are involved
unique_values = list(set(modified_parameter_values[parameter_key].flatten()))
# Resolve and use value for data frame
if len(unique_values) > 1 and not sum_values :
# Use multiplier percentage data_frame_percent_format in data frame