1#! /usr/bin/env python 2# -*- coding: utf-8 -*- 3 4############################################################################### 5# Copyright (c) 2012-7 Bryce Adelstein Lelbach aka wash <brycelelbach@gmail.com> 6# 7# Distributed under the Boost Software License, Version 1.0. (See accompanying 8# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9############################################################################### 10 11############################################################################### 12# Copyright (c) 2018 NVIDIA Corporation 13# 14# Licensed under the Apache License, Version 2.0 (the "License"); 15# you may not use this file except in compliance with the License. 16# You may obtain a copy of the License at 17# 18# http://www.apache.org/licenses/LICENSE-2.0 19# 20# Unless required by applicable law or agreed to in writing, software 21# distributed under the License is distributed on an "AS IS" BASIS, 22# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23# See the License for the specific language governing permissions and 24# limitations under the License. 25############################################################################### 26 27# XXX Put code shared with `compare_benchmark_results.py` in a common place. 28 29# XXX Relative uncertainty. 30 31from sys import exit, stdout 32 33from os.path import splitext 34 35from itertools import imap # Lazy map. 36 37from math import sqrt, log10, floor 38 39from collections import deque 40 41from argparse import ArgumentParser as argument_parser 42 43from csv import DictReader as csv_dict_reader 44from csv import DictWriter as csv_dict_writer 45 46from re import compile as regex_compile 47 48############################################################################### 49 50def unpack_tuple(f): 51 """Return a unary function that calls `f` with its argument unpacked.""" 52 return lambda args: f(*iter(args)) 53 54def strip_dict(d): 55 """Strip leading and trailing whitespace from all keys and values in `d`.""" 56 d.update({key: value.strip() for (key, value) in d.items()}) 57 58def merge_dicts(d0, d1): 59 """Create a new `dict` that is the union of `dict`s `d0` and `d1`.""" 60 d = d0.copy() 61 d.update(d1) 62 return d 63 64def strip_list(l): 65 """Strip leading and trailing whitespace from all values in `l`.""" 66 for i, value in enumerate(l): l[i] = value.strip() 67 68############################################################################### 69 70def int_or_float(x): 71 """Convert `x` to either `int` or `float`, preferring `int`. 72 73 Raises: 74 ValueError : If `x` is not convertible to either `int` or `float` 75 """ 76 try: 77 return int(x) 78 except ValueError: 79 return float(x) 80 81def try_int_or_float(x): 82 """Try to convert `x` to either `int` or `float`, preferring `int`. `x` is 83 returned unmodified if conversion fails. 84 """ 85 try: 86 return int_or_float(x) 87 except ValueError: 88 return x 89 90############################################################################### 91 92def find_significant_digit(x): 93 """Return the significant digit of the number x. The result is the number of 94 digits after the decimal place to round to (negative numbers indicate rounding 95 before the decimal place).""" 96 if x == 0: return 0 97 return -int(floor(log10(abs(x)))) 98 99def round_with_int_conversion(x, ndigits = None): 100 """Rounds `x` to `ndigits` after the the decimal place. If `ndigits` is less 101 than 1, convert the result to `int`. If `ndigits` is `None`, the significant 102 digit of `x` is used.""" 103 if ndigits is None: ndigits = find_significant_digit(x) 104 x_rounded = round(x, ndigits) 105 return int(x_rounded) if ndigits < 1 else x_rounded 106 107############################################################################### 108 109class measured_variable(object): 110 """A meta-variable representing measured data. It is composed of three raw 111 variables plus units meta-data. 112 113 Attributes: 114 quantity (`str`) : 115 Name of the quantity variable of this object. 116 uncertainty (`str`) : 117 Name of the uncertainty variable of this object. 118 sample_size (`str`) : 119 Name of the sample size variable of this object. 120 units (units class or `None`) : 121 The units the value is measured in. 122 """ 123 124 def __init__(self, quantity, uncertainty, sample_size, units = None): 125 self.quantity = quantity 126 self.uncertainty = uncertainty 127 self.sample_size = sample_size 128 self.units = units 129 130 def as_tuple(self): 131 return (self.quantity, self.uncertainty, self.sample_size, self.units) 132 133 def __iter__(self): 134 return iter(self.as_tuple()) 135 136 def __str__(self): 137 return str(self.as_tuple()) 138 139 def __repr__(self): 140 return str(self) 141 142class measured_value(object): 143 """An object that represents a value determined by multiple measurements. 144 145 Attributes: 146 quantity (scalar) : 147 The quantity of the value, e.g. the arithmetic mean. 148 uncertainty (scalar) : 149 The measurement uncertainty, e.g. the sample standard deviation. 150 sample_size (`int`) : 151 The number of observations contributing to the value. 152 units (units class or `None`) : 153 The units the value is measured in. 154 """ 155 156 def __init__(self, quantity, uncertainty, sample_size = 1, units = None): 157 self.quantity = quantity 158 self.uncertainty = uncertainty 159 self.sample_size = sample_size 160 self.units = units 161 162 def as_tuple(self): 163 return (self.quantity, self.uncertainty, self.sample_size, self.units) 164 165 def __iter__(self): 166 return iter(self.as_tuple()) 167 168 def __str__(self): 169 return str(self.as_tuple()) 170 171 def __repr__(self): 172 return str(self) 173 174############################################################################### 175 176def arithmetic_mean(X): 177 """Computes the arithmetic mean of the sequence `X`. 178 179 Let: 180 181 * `n = len(X)`. 182 * `u` denote the arithmetic mean of `X`. 183 184 .. math:: 185 186 u = \frac{\sum_{i = 0}^{n - 1} X_i}{n} 187 """ 188 return sum(X) / len(X) 189 190def sample_variance(X, u = None): 191 """Computes the sample variance of the sequence `X`. 192 193 Let: 194 195 * `n = len(X)`. 196 * `u` denote the arithmetic mean of `X`. 197 * `s` denote the sample standard deviation of `X`. 198 199 .. math:: 200 201 v = \frac{\sum_{i = 0}^{n - 1} (X_i - u)^2}{n - 1} 202 203 Args: 204 X (`Iterable`) : The sequence of values. 205 u (number) : The arithmetic mean of `X`. 206 """ 207 if u is None: u = arithmetic_mean(X) 208 return sum(imap(lambda X_i: (X_i - u) ** 2, X)) / (len(X) - 1) 209 210def sample_standard_deviation(X, u = None, v = None): 211 """Computes the sample standard deviation of the sequence `X`. 212 213 Let: 214 215 * `n = len(X)`. 216 * `u` denote the arithmetic mean of `X`. 217 * `v` denote the sample variance of `X`. 218 * `s` denote the sample standard deviation of `X`. 219 220 .. math:: 221 222 s &= \sqrt{v} 223 &= \sqrt{\frac{\sum_{i = 0}^{n - 1} (X_i - u)^2}{n - 1}} 224 225 Args: 226 X (`Iterable`) : The sequence of values. 227 u (number) : The arithmetic mean of `X`. 228 v (number) : The sample variance of `X`. 229 """ 230 if u is None: u = arithmetic_mean(X) 231 if v is None: v = sample_variance(X, u) 232 return sqrt(v) 233 234def combine_sample_size(As): 235 """Computes the combined sample variance of a group of `measured_value`s. 236 237 Let: 238 239 * `g = len(As)`. 240 * `n_i = As[i].samples`. 241 * `n` denote the combined sample size of `As`. 242 243 .. math:: 244 245 n = \sum{i = 0}^{g - 1} n_i 246 """ 247 return sum(imap(unpack_tuple(lambda u_i, s_i, n_i, t_i: n_i), As)) 248 249def combine_arithmetic_mean(As, n = None): 250 """Computes the combined arithmetic mean of a group of `measured_value`s. 251 252 Let: 253 254 * `g = len(As)`. 255 * `u_i = As[i].quantity`. 256 * `n_i = As[i].samples`. 257 * `n` denote the combined sample size of `As`. 258 * `u` denote the arithmetic mean of the quantities of `As`. 259 260 .. math:: 261 262 u = \frac{\sum{i = 0}^{g - 1} n_i u_i}{n} 263 """ 264 if n is None: n = combine_sample_size(As) 265 return sum(imap(unpack_tuple(lambda u_i, s_i, n_i, t_i: n_i * u_i), As)) / n 266 267def combine_sample_variance(As, n = None, u = None): 268 """Computes the combined sample variance of a group of `measured_value`s. 269 270 Let: 271 272 * `g = len(As)`. 273 * `u_i = As[i].quantity`. 274 * `s_i = As[i].uncertainty`. 275 * `n_i = As[i].samples`. 276 * `n` denote the combined sample size of `As`. 277 * `u` denote the arithmetic mean of the quantities of `As`. 278 * `v` denote the sample variance of `X`. 279 280 .. math:: 281 282 v = \frac{(\sum_{i = 0}^{g - 1} n_i (u_i - u)^2 + s_i^2 (n_i - 1))}{n - 1} 283 284 Args: 285 As (`Iterable` of `measured_value`s) : The sequence of values. 286 n (number) : The combined sample sizes of `As`. 287 u (number) : The combined arithmetic mean of `As`. 288 """ 289 if n <= 1: return 0 290 if n is None: n = combine_sample_size(As) 291 if u is None: u = combine_arithmetic_mean(As, n) 292 return sum(imap(unpack_tuple( 293 lambda u_i, s_i, n_i, t_i: n_i * (u_i - u) ** 2 + (s_i ** 2) * (n_i - 1) 294 ), As)) / (n - 1) 295 296def combine_sample_standard_deviation(As, n = None, u = None, v = None): 297 """Computes the combined sample standard deviation of a group of 298 `measured_value`s. 299 300 Let: 301 302 * `g = len(As)`. 303 * `u_i = As[i].quantity`. 304 * `s_i = As[i].uncertainty`. 305 * `n_i = As[i].samples`. 306 * `n` denote the combined sample size of `As`. 307 * `u` denote the arithmetic mean of the quantities of `As`. 308 * `v` denote the sample variance of `X`. 309 * `s` denote the sample standard deviation of `X`. 310 311 .. math:: 312 313 s &= \sqrt{v} 314 &= \sqrt{\frac{(\sum_{i = 0}^{g - 1} n_i (u_i - u)^2 + s_i^2 (n_i - 1))}{n - 1}} 315 316 Args: 317 As (`Iterable` of `measured_value`s) : The sequence of values. 318 n (number) : The combined sample sizes of `As`. 319 u (number) : The combined arithmetic mean of `As`. 320 v (number) : The combined sample variance of `As`. 321 """ 322 if n <= 1: return 0 323 if n is None: n = combine_sample_size(As) 324 if u is None: u = combine_arithmetic_mean(As, n) 325 if v is None: v = combine_sample_variance(As, n, u) 326 return sqrt(v) 327 328############################################################################### 329 330def process_program_arguments(): 331 ap = argument_parser( 332 description = ( 333 "Aggregates the results of multiple runs of benchmark results stored in " 334 "CSV format." 335 ) 336 ) 337 338 ap.add_argument( 339 "-d", "--dependent-variable", 340 help = ("Treat the specified three variables as a dependent variable. The " 341 "1st variable is the measured quantity, the 2nd is the uncertainty " 342 "of the measurement and the 3rd is the sample size. The defaults " 343 "are the dependent variables of Thrust's benchmark suite. May be " 344 "specified multiple times."), 345 action = "append", type = str, dest = "dependent_variables", 346 metavar = "QUANTITY,UNCERTAINTY,SAMPLES" 347 ) 348 349 ap.add_argument( 350 "-p", "--preserve-whitespace", 351 help = ("Don't trim leading and trailing whitespace from each CSV cell."), 352 action = "store_true", default = False 353 ) 354 355 ap.add_argument( 356 "-o", "--output-file", 357 help = ("The file that results are written to. If `-`, results are " 358 "written to stdout."), 359 action = "store", type = str, default = "-", 360 metavar = "OUTPUT" 361 ) 362 363 ap.add_argument( 364 "input_files", 365 help = ("Input CSV files. The first two rows should be a header. The 1st " 366 "header row specifies the name of each variable, and the 2nd " 367 "header row specifies the units for that variable."), 368 type = str, nargs = "+", 369 metavar = "INPUTS" 370 ) 371 372 return ap.parse_args() 373 374############################################################################### 375 376def filter_comments(f, s = "#"): 377 """Return an iterator to the file `f` which filters out all lines beginning 378 with `s`.""" 379 return filter(lambda line: not line.startswith(s), f) 380 381############################################################################### 382 383class io_manager(object): 384 """Manages I/O operations and represents the input data as an `Iterable` 385 sequence of `dict`s. 386 387 It is `Iterable` and an `Iterator`. It can be used with `with`. 388 389 Attributes: 390 preserve_whitespace (`bool`) : 391 If `False`, leading and trailing whitespace is stripped from each CSV cell. 392 writer (`csv_dict_writer`) : 393 CSV writer object that the output is written to. 394 output_file (`file` or `stdout`) : 395 The output `file` object. 396 readers (`list` of `csv_dict_reader`s) : 397 List of input files as CSV reader objects. 398 input_files (list of `file`s) : 399 List of input `file` objects. 400 variable_names (`list` of `str`s) : 401 Names of the variables, in order. 402 variable_units (`list` of `str`s) : 403 Units of the variables, in order. 404 """ 405 406 def __init__(self, input_files, output_file, preserve_whitespace = True): 407 """Read input files and open the output file and construct a new `io_manager` 408 object. 409 410 If `preserve_whitespace` is `False`, leading and trailing whitespace is 411 stripped from each CSV cell. 412 413 Raises 414 AssertionError : 415 If `len(input_files) <= 0` or `type(preserve_whitespace) != bool`. 416 """ 417 assert len(input_files) > 0, "No input files provided." 418 419 assert type(preserve_whitespace) == bool 420 421 self.preserve_whitespace = preserve_whitespace 422 423 self.readers = deque() 424 425 self.variable_names = None 426 self.variable_units = None 427 428 self.input_files = deque() 429 430 for input_file in input_files: 431 input_file_object = open(input_file) 432 reader = csv_dict_reader(filter_comments(input_file_object)) 433 434 if not self.preserve_whitespace: 435 strip_list(reader.fieldnames) 436 437 if self.variable_names is None: 438 self.variable_names = reader.fieldnames 439 else: 440 # Make sure all inputs have the same schema. 441 assert self.variable_names == reader.fieldnames, \ 442 "Input file (`" + input_file + "`) variable schema `" + \ 443 str(reader.fieldnames) + "` does not match the variable schema `" + \ 444 str(self.variable_names) + "`." 445 446 # Consume the next row, which should be the second line of the header. 447 variable_units = reader.next() 448 449 if not self.preserve_whitespace: 450 strip_dict(variable_units) 451 452 if self.variable_units is None: 453 self.variable_units = variable_units 454 else: 455 # Make sure all inputs have the same units schema. 456 assert self.variable_units == variable_units, \ 457 "Input file (`" + input_file + "`) units schema `" + \ 458 str(variable_units) + "` does not match the units schema `" + \ 459 str(self.variable_units) + "`." 460 461 self.readers.append(reader) 462 self.input_files.append(input_file_object) 463 464 if output_file == "-": # Output to stdout. 465 self.output_file = stdout 466 else: # Output to user-specified file. 467 self.output_file = open(output_file, "w") 468 469 self.writer = csv_dict_writer( 470 self.output_file, fieldnames = self.variable_names 471 ) 472 473 def __enter__(self): 474 """Called upon entering a `with` statement.""" 475 return self 476 477 def __exit__(self, *args): 478 """Called upon exiting a `with` statement.""" 479 if self.output_file is stdout: 480 self.output_file = None 481 elif self.output_file is not None: 482 self.output_file.__exit__(*args) 483 484 for input_file in self.input_files: 485 input_file.__exit__(*args) 486 487 ############################################################################# 488 # Input Stream. 489 490 def __iter__(self): 491 """Return an iterator to the input sequence. 492 493 This is a requirement for the `Iterable` protocol. 494 """ 495 return self 496 497 def next(self): 498 """Consume and return the next record (a `dict` representing a CSV row) in 499 the input. 500 501 This is a requirement for the `Iterator` protocol. 502 503 Raises: 504 StopIteration : If there is no more input. 505 """ 506 if len(self.readers) == 0: 507 raise StopIteration() 508 509 try: 510 row = self.readers[0].next() 511 if not self.preserve_whitespace: strip_dict(row) 512 return row 513 except StopIteration: 514 # The current reader is empty, so pop it, pop it's input file, close the 515 # input file, and then call ourselves again. 516 self.readers.popleft() 517 self.input_files.popleft().close() 518 return self.next() 519 520 ############################################################################# 521 # Output. 522 523 def write_header(self): 524 """Write the header for the output CSV file.""" 525 # Write the first line of the header. 526 self.writer.writeheader() 527 528 # Write the second line of the header. 529 self.writer.writerow(self.variable_units) 530 531 def write(self, d): 532 """Write a record (a `dict`) to the output CSV file.""" 533 self.writer.writerow(d) 534 535############################################################################### 536 537class dependent_variable_parser(object): 538 """Parses a `--dependent-variable=AVG,STDEV,TRIALS` command line argument.""" 539 540 ############################################################################# 541 # Grammar 542 543 # Parse a variable_name. 544 variable_name_rule = r'[^,]+' 545 546 # Parse a variable classification. 547 dependent_variable_rule = r'(' + variable_name_rule + r')' \ 548 + r',' \ 549 + r'(' + variable_name_rule + r')' \ 550 + r',' \ 551 + r'(' + variable_name_rule + r')' 552 553 engine = regex_compile(dependent_variable_rule) 554 555 ############################################################################# 556 557 def __call__(self, s): 558 """Parses the string `s` with the form "AVG,STDEV,TRIALS". 559 560 Returns: 561 A `measured_variable`. 562 563 Raises: 564 AssertionError : If parsing fails. 565 """ 566 567 match = self.engine.match(s) 568 569 assert match is not None, \ 570 "Dependent variable (-d) `" +s+ "` is invalid, the format is " + \ 571 "`AVG,STDEV,TRIALS`." 572 573 return measured_variable(match.group(1), match.group(2), match.group(3)) 574 575############################################################################### 576 577class record_aggregator(object): 578 """Consumes and combines records and represents the result as an `Iterable` 579 sequence of `dict`s. 580 581 It is `Iterable` and an `Iterator`. 582 583 Attributes: 584 dependent_variables (`list` of `measured_variable`s) : 585 A list of dependent variables provided on the command line. 586 dataset (`dict`) : 587 A mapping of distinguishing (e.g. control + independent) values (`tuple`s 588 of variable-quantity pairs) to `list`s of dependent values (`dict`s from 589 variables to lists of cells). 590 in_order_dataset_keys : 591 A list of unique dataset keys (e.g. distinguishing variables) in order of 592 appearance. 593 """ 594 595 parse_dependent_variable = dependent_variable_parser() 596 597 def __init__(self, raw_dependent_variables): 598 """Parse dependent variables and construct a new `record_aggregator` object. 599 600 Raises: 601 AssertionError : If parsing of dependent variables fails. 602 """ 603 self.dependent_variables = [] 604 605 if raw_dependent_variables is not None: 606 for variable in raw_dependent_variables: 607 self.dependent_variables.append(self.parse_dependent_variable(variable)) 608 609 self.dataset = {} 610 611 self.in_order_dataset_keys = deque() 612 613 ############################################################################# 614 # Insertion. 615 616 def append(self, record): 617 """Add `record` to the dataset. 618 619 Raises: 620 ValueError : If any `str`-to-numeric conversions fail. 621 """ 622 # The distinguishing variables are the control and independent variables. 623 # They form the key for each record in the dataset. Records with the same 624 # distinguishing variables are treated as observations of the same data 625 # point. 626 dependent_values = {} 627 628 # To allow the same sample size variable to be used for multiple dependent 629 # variables, we don't pop sample size variables until we're done processing 630 # all variables. 631 sample_size_variables = [] 632 633 # Separate the dependent values from the distinguishing variables and 634 # perform `str`-to-numeric conversions. 635 for variable in self.dependent_variables: 636 quantity, uncertainty, sample_size, units = variable.as_tuple() 637 638 dependent_values[quantity] = [int_or_float(record.pop(quantity))] 639 dependent_values[uncertainty] = [int_or_float(record.pop(uncertainty))] 640 dependent_values[sample_size] = [int(record[sample_size])] 641 642 sample_size_variables.append(sample_size) 643 644 # Pop sample size variables. 645 for sample_size_variable in sample_size_variables: 646 # Allowed to fail, as we may have duplicates. 647 record.pop(sample_size_variable, None) 648 649 # `dict`s aren't hashable, so create a tuple of key-value pairs. 650 distinguishing_values = tuple(record.items()) 651 652 if distinguishing_values in self.dataset: 653 # These distinguishing values already exist, so get the `dict` they're 654 # mapped to, look up each key in `dependent_values` in the `dict`, and 655 # add the corresponding quantity in `dependent_values` to the list in the 656 # the `dict`. 657 for variable, columns in dependent_values.iteritems(): 658 self.dataset[distinguishing_values][variable] += columns 659 else: 660 # These distinguishing values aren't in the dataset, so add them and 661 # record them in `in_order_dataset_keys`. 662 self.dataset[distinguishing_values] = dependent_values 663 self.in_order_dataset_keys.append(distinguishing_values) 664 665 ############################################################################# 666 # Postprocessing. 667 668 def combine_dependent_values(self, dependent_values): 669 """Takes a mapping of dependent variables to lists of cells and returns 670 a new mapping with the cells combined. 671 672 Raises: 673 AssertionError : If class invariants were violated. 674 """ 675 combined_dependent_values = dependent_values.copy() 676 677 for variable in self.dependent_variables: 678 quantity, uncertainty, sample_size, units = variable.as_tuple() 679 680 quantities = dependent_values[quantity] 681 uncertainties = dependent_values[uncertainty] 682 sample_sizes = dependent_values[sample_size] 683 684 if type(sample_size) is list: 685 # Sample size hasn't been combined yet. 686 assert len(quantities) == len(uncertainties) \ 687 and len(uncertainties) == len(sample_sizes), \ 688 "Length of quantities list `(" + str(len(quantities)) + ")`, " + \ 689 "length of uncertainties list `(" + str(len(uncertainties)) + \ 690 "),` and length of sample sizes list `(" + str(len(sample_sizes)) + \ 691 ")` are not the same." 692 else: 693 # Another dependent variable that uses our sample size has combined it 694 # already. 695 assert len(quantities) == len(uncertainties), \ 696 "Length of quantities list `(" + str(len(quantities)) + ")` and " + \ 697 "length of uncertainties list `(" + str(len(uncertainties)) + \ 698 ")` are not the same." 699 700 # Convert the three separate `list`s into one list of `measured_value`s. 701 measured_values = [] 702 703 for i in range(len(quantities)): 704 mv = measured_value( 705 quantities[i], uncertainties[i], sample_sizes[i], units 706 ) 707 708 measured_values.append(mv) 709 710 # Combine the `measured_value`s. 711 combined_sample_size = combine_sample_size( 712 measured_values 713 ) 714 715 combined_arithmetic_mean = combine_arithmetic_mean( 716 measured_values, combined_sample_size 717 ) 718 719 combined_sample_standard_deviation = combine_sample_standard_deviation( 720 measured_values, combined_sample_size, combined_arithmetic_mean 721 ) 722 723 # Round the quantity and uncertainty to the significant digit of 724 # uncertainty and insert the combined values into the results. 725 sigdig = find_significant_digit(combined_sample_standard_deviation) 726 727# combined_arithmetic_mean = round_with_int_conversion( 728# combined_arithmetic_mean, sigdig 729# ) 730 731# combined_sample_standard_deviation = round_with_int_conversion( 732# combined_sample_standard_deviation, sigdig 733# ) 734 735 combined_dependent_values[quantity] = combined_arithmetic_mean 736 combined_dependent_values[uncertainty] = combined_sample_standard_deviation 737 combined_dependent_values[sample_size] = combined_sample_size 738 739 return combined_dependent_values 740 741 ############################################################################# 742 # Output Stream. 743 744 def __iter__(self): 745 """Return an iterator to the output sequence of separated distinguishing 746 variables and dependent variables (a tuple of two `dict`s). 747 748 This is a requirement for the `Iterable` protocol. 749 """ 750 return self 751 752 def records(self): 753 """Return an iterator to the output sequence of CSV rows (`dict`s of 754 variables to values). 755 """ 756 return imap(unpack_tuple(lambda dist, dep: merge_dicts(dist, dep)), self) 757 758 def next(self): 759 """Produce the components of the next output record - a tuple of two 760 `dict`s. The first `dict` is a mapping of distinguishing variables to 761 distinguishing values, the second `dict` is a mapping of dependent 762 variables to combined dependent values. Combining the two dicts forms a 763 CSV row suitable for output. 764 765 This is a requirement for the `Iterator` protocol. 766 767 Raises: 768 StopIteration : If there is no more output. 769 AssertionError : If class invariants were violated. 770 """ 771 assert len(self.dataset.keys()) == len(self.in_order_dataset_keys), \ 772 "Number of dataset keys (`" + str(len(self.dataset.keys())) + \ 773 "`) is not equal to the number of keys in the ordering list (`" + \ 774 str(len(self.in_order_dataset_keys)) + "`)." 775 776 if len(self.in_order_dataset_keys) == 0: 777 raise StopIteration() 778 779 # Get the next set of distinguishing values and convert them to a `dict`. 780 raw_distinguishing_values = self.in_order_dataset_keys.popleft() 781 distinguishing_values = dict(raw_distinguishing_values) 782 783 dependent_values = self.dataset.pop(raw_distinguishing_values) 784 785 combined_dependent_values = self.combine_dependent_values(dependent_values) 786 787 return (distinguishing_values, combined_dependent_values) 788 789############################################################################### 790 791args = process_program_arguments() 792 793if args.dependent_variables is None: 794 args.dependent_variables = [ 795 "STL Average Walltime,STL Walltime Uncertainty,STL Trials", 796 "STL Average Throughput,STL Throughput Uncertainty,STL Trials", 797 "Thrust Average Walltime,Thrust Walltime Uncertainty,Thrust Trials", 798 "Thrust Average Throughput,Thrust Throughput Uncertainty,Thrust Trials" 799 ] 800 801# Read input files and open the output file. 802with io_manager(args.input_files, 803 args.output_file, 804 args.preserve_whitespace) as iom: 805 # Parse dependent variable options. 806 ra = record_aggregator(args.dependent_variables) 807 808 # Add all input data to the `record_aggregator`. 809 for record in iom: 810 ra.append(record) 811 812 iom.write_header() 813 814 # Write combined results out. 815 for record in ra.records(): 816 iom.write(record) 817 818