Skip to content

Old API for Developers

This API still exists, but I don’t think they were used publicly anywhere. The new API should be preferred.

Entry point

Main file for perprof.

Classes

perprof.main.ParserOptions

Bases: TypedDict

Type definition for parser options dictionary.

Source code in perprof/main.py
70
71
72
73
74
75
76
77
78
79
80
81
class ParserOptions(TypedDict):
    """Type definition for parser options dictionary."""

    free_format: bool
    files: list[str]
    success: list[str]
    maxtime: float
    mintime: float
    compare: str
    unc: bool
    infeas_tol: float
    subset: list[str]

perprof.main.ProfilerOptions

Bases: TypedDict

Type definition for profiler options dictionary.

Source code in perprof/main.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
class ProfilerOptions(TypedDict):
    """Type definition for profiler options dictionary."""

    lang: str
    cache: bool
    files: list[str]
    force: bool
    standalone: bool
    semilog: bool
    black_and_white: bool
    output: str | None
    pgfplot_version: float | None
    tau: float | None
    pdf_verbose: bool
    title: str | None
    xlabel: str
    ylabel: str
    background: tuple[int, int, int] | None
    page_background: tuple[int, int, int] | None
    output_format: str | None

Functions

perprof.main.main()

Run the perprof command-line tool.

Orchestrates the performance profile generation workflow: 1. Parse command-line arguments and configure logging 2. Process arguments into option dictionaries 3. Select backend and generate profile output

Handles errors gracefully with user-friendly messages.

Example usage

$ perprof –bokeh data1.txt data2.txt -o comparison.html $ perprof –matplotlib –pdf solver1.csv solver2.csv $ perprof –tikz –tex algorithm1.yaml algorithm2.yaml

Raises:

  • SystemExit –

    On argument parsing errors or critical failures.

Source code in perprof/main.py
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
def main() -> None:
    """Run the perprof command-line tool.

    Orchestrates the performance profile generation workflow:
    1. Parse command-line arguments and configure logging
    2. Process arguments into option dictionaries
    3. Select backend and generate profile output

    Handles errors gracefully with user-friendly messages.

    Example usage:
        $ perprof --bokeh data1.txt data2.txt -o comparison.html
        $ perprof --matplotlib --pdf solver1.csv solver2.csv
        $ perprof --tikz --tex algorithm1.yaml algorithm2.yaml

    Raises:
        SystemExit: On argument parsing errors or critical failures.
    """
    try:
        args = set_arguments(sys.argv[1:])

        # Initialize logging
        setup_logging(verbose=args.verbose, debug=args.debug, log_file=args.log_file)

        logger = logging.getLogger("perprof.main")
        logger.info("Starting perprof with %d input files", len(args.file_name))

        if args.debug:
            logger.debug("Arguments: %s", vars(args))

        parser_options, profiler_options = process_arguments(args)

        if args.debug:
            logger.debug("Parser options: %s", parser_options)
            logger.debug("Profiler options: %s", profiler_options)

        if args.bokeh:
            logger.info(
                "Using Bokeh backend for %s output", profiler_options["output_format"]
            )
            from . import bokeh

            bokeh_profiler = bokeh.Profiler(parser_options, profiler_options)
            bokeh_profiler.plot()
            logger.info("Bokeh plot generation completed")
        elif args.mp:
            logger.info(
                "Using matplotlib backend for %s output",
                profiler_options["output_format"],
            )
            from . import matplotlib

            mp_profiler = matplotlib.Profiler(parser_options, profiler_options)
            mp_profiler.plot()
            logger.info("Matplotlib plot generation completed")
        elif args.tikz:
            if profiler_options["output_format"] == "pdf" and args.output is None:
                error_msg = _(
                    "ERROR: When using PDF output, you need to provide "
                    "the name of the output file."
                )
                logger.error(error_msg)
                print(error_msg)
            else:
                logger.info(
                    "Using TikZ backend for %s output",
                    profiler_options["output_format"],
                )
                from . import tikz

                tikz_profiler = tikz.Profiler(parser_options, profiler_options)
                tikz_profiler.plot()
                logger.info("TikZ plot generation completed")
        elif args.raw:
            logger.info("Generating raw data output")
            from . import prof

            print("raw")

            print(prof.Pdata(parser_options, profiler_options))
        elif args.table:
            logger.info("Generating robustness/efficiency table")
            from . import prof

            pdata = prof.Pdata(parser_options, profiler_options)
            pdata.print_rob_eff_table()
    except ValueError as error:
        logger = logging.getLogger("perprof.main")
        logger.error("Input validation error: %s", error)
        print(error)
    except NotImplementedError as error:
        logger = logging.getLogger("perprof.main")
        logger.error("Feature not implemented: %s", error)
        print(error)
    except Exception as error:
        logger = logging.getLogger("perprof.main")
        logger.exception("Unexpected error occurred")
        print(f"Unexpected error: {error}")
        sys.exit(1)

perprof.main.process_arguments(args)

Convert command-line arguments into parser and profiler option dictionaries.

Separates data processing options from visualization options and validates backend/format compatibility.

Parameters:

  • args (Namespace) –

    Parsed command-line arguments from argparse.

Returns:

  • tuple[ParserOptions, ProfilerOptions] –

    tuple[ParserOptions, ProfilerOptions]: A tuple containing: - ParserOptions: Configuration for data parsing and processing - ProfilerOptions: Configuration for profile visualization

Raises:

Example

import argparse

Create a minimal mock args object - full args object would be much larger
This demonstrates the function’s purpose rather than testing all attributes

pass # Simplified example due to extensive argument requirements

Source code in perprof/main.py
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
def process_arguments(
    args: argparse.Namespace,
) -> tuple[ParserOptions, ProfilerOptions]:
    """Convert command-line arguments into parser and profiler option dictionaries.

    Separates data processing options from visualization options and validates
    backend/format compatibility.

    Args:
        args (argparse.Namespace): Parsed command-line arguments from argparse.

    Returns:
        tuple[ParserOptions, ProfilerOptions]: A tuple containing:
            - ParserOptions: Configuration for data parsing and processing
            - ProfilerOptions: Configuration for profile visualization

    Raises:
        NotImplementedError: If an unsupported output format is specified for a backend.
        AssertionError: If RGB color values are invalid.

    Example:
        >>> import argparse
        >>> # Create a minimal mock args object - full args object would be much larger
        >>> # This demonstrates the function's purpose rather than testing all attributes
        >>> pass  # Simplified example due to extensive argument requirements
    """
    parser_options: ParserOptions = {
        "free_format": args.free_format,
        "files": args.file_name,
        "success": args.success.split(","),
        "maxtime": args.maxtime,
        "mintime": args.mintime,
        "compare": args.compare,
        "unc": args.unconstrained,
        "infeas_tol": args.infeasibility_tolerance,
        "subset": [],  # Will be set below if args.subset exists
    }

    profiler_options: ProfilerOptions = {
        "lang": args.lang,
        "cache": args.cache,
        "files": args.file_name,
        "force": args.force,
        "standalone": args.standalone,
        "semilog": args.semilog,
        "black_and_white": args.black_and_white,
        "output": args.output,
        "pgfplot_version": args.pgfplotcompat,
        "tau": args.tau,
        "pdf_verbose": args.pdf_verbose,
        "title": args.title,
        "xlabel": args.xlabel,
        "ylabel": args.ylabel,
        "background": None,  # Will be set below if args.background exists
        "page_background": None,  # Will be set below if args.page_background exists
        "output_format": None,  # Will be set below based on args
    }

    if args.no_title:
        profiler_options["title"] = None

    if args.background is None:
        profiler_options["background"] = None
    else:
        # Set a tuple of integer
        background_tuple = tuple(int(i) for i in args.background.split(","))
        assert len(background_tuple) == 3, _("RGB for background must have 3 integers")
        profiler_options["background"] = background_tuple
    if args.page_background is None:
        profiler_options["page_background"] = None
    else:
        page_background_tuple = tuple(int(i) for i in args.page_background.split(","))
        assert len(page_background_tuple) == 3, _(
            "RGB for page background must have 3 integers"
        )
        profiler_options["page_background"] = page_background_tuple

    if args.html:
        profiler_options["output_format"] = "html"
    elif args.eps:
        profiler_options["output_format"] = "eps"
    elif args.pdf:
        profiler_options["output_format"] = "pdf"
    elif args.png:
        profiler_options["output_format"] = "png"
    elif args.ps:
        profiler_options["output_format"] = "ps"
    elif args.svg:
        profiler_options["output_format"] = "svg"
    elif args.tex:
        profiler_options["output_format"] = "tex"
    elif args.bokeh:
        profiler_options["output_format"] = "html"
    elif args.mp:
        profiler_options["output_format"] = "png"
    elif args.tikz:
        profiler_options["output_format"] = "pdf"
    else:
        profiler_options["output_format"] = None

    output_format = profiler_options["output_format"]
    if args.bokeh and output_format not in SUPPORT_BOKEH:
        raise NotImplementedError(
            _("Output option {} not supported by bokeh").format(
                output_format.upper() if output_format else "None"
            )
        )
    if args.mp and output_format not in SUPPORT_MP:
        raise NotImplementedError(
            _("Output option {} not supported by matplotlib").format(
                output_format.upper() if output_format else "None"
            )
        )
    if args.tikz and output_format not in SUPPORT_TIKZ:
        raise NotImplementedError(
            _("Output option {} not supported by TikZ").format(
                output_format.upper() if output_format else "None"
            )
        )
    if args.raw and output_format:
        raise NotImplementedError(
            _("--raw does not support output except standard output")
        )
    if args.table and output_format:
        raise NotImplementedError(_("--table only write to .tex or to standard output"))

    if args.subset:
        with open(args.subset, encoding="utf-8") as subset_file:
            parser_options["subset"] = [line.strip() for line in subset_file]
        if len(parser_options["subset"]) == 0:
            raise AttributeError(_("ERROR: Subset is empty"))

    return parser_options, profiler_options

perprof.main.set_arguments(args)

Parse and validate command-line arguments for perprof.

Defines the CLI argument parser with backend options, output formats, and data processing parameters.

Parameters:

  • args (list[str]) –

    Command-line arguments (typically sys.argv[1:]).

Returns:

  • Namespace –

    argparse.Namespace: Parsed and validated arguments ready for processing.

Example
Parse arguments for Bokeh output

args = set_arguments([“–bokeh”, “data1.txt”, “data2.txt”, “-o”, “out.html”]) args.bokeh True args.file_name [‘data1.txt’, ‘data2.txt’] args.output ‘out.html’

Source code in perprof/main.py
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
def set_arguments(args: list[str]) -> argparse.Namespace:
    """Parse and validate command-line arguments for perprof.

    Defines the CLI argument parser with backend options, output formats,
    and data processing parameters.

    Args:
        args (list[str]): Command-line arguments (typically sys.argv[1:]).

    Returns:
        argparse.Namespace: Parsed and validated arguments ready for processing.

    Example:
        >>> # Parse arguments for Bokeh output
        >>> args = set_arguments(["--bokeh", "data1.txt", "data2.txt", "-o", "out.html"])
        >>> args.bokeh
        True
        >>> args.file_name
        ['data1.txt', 'data2.txt']
        >>> args.output
        'out.html'
    """
    parser = argparse.ArgumentParser(
        description=_(
            "A python module for performance profiling "
            "(as described by Dolan and Moré)."
        ),
        fromfile_prefix_chars="@",
    )

    backend_args = parser.add_argument_group(_("Backend options"))
    backend = backend_args.add_mutually_exclusive_group(required=True)
    backend.add_argument(
        "--bokeh",
        action="store_true",
        help=_("Use bokeh as backend for the plot. Default output: HTML"),
    )
    backend.add_argument(
        "--mp",
        action="store_true",
        help=_("Use matplotlib as backend for the plot. Default output: PNG"),
    )
    backend.add_argument(
        "--tikz",
        action="store_true",
        help=_("Use LaTex/TikZ/pgfplots as backend for the plot. Default output: PDF"),
    )
    backend.add_argument(
        "--raw",
        action="store_true",
        help=_("Print raw data. Default output: standard output"),
    )
    backend.add_argument(
        "--table",
        action="store_true",
        help=_("Print table of robustness and efficiency"),
    )

    output_format_args = parser.add_argument_group(_("Output formats"))
    output_format = output_format_args.add_mutually_exclusive_group()
    output_format.add_argument(
        "--html", action="store_true", help=_("The output file will be a HTML file")
    )
    output_format.add_argument(
        "--eps", action="store_true", help=_("The output file will be a EPS file")
    )
    output_format.add_argument(
        "--pdf", action="store_true", help=_("The output file will be a PDF file")
    )
    output_format.add_argument(
        "--png", action="store_true", help=_("The output file will be a PNG file")
    )
    output_format.add_argument(
        "--ps", action="store_true", help=_("The output file will be a PS file")
    )
    output_format.add_argument(
        "--svg", action="store_true", help=_("The output file will be a SVG file")
    )
    output_format.add_argument(
        "--tex", action="store_true", help=_("The output file will be a (La)TeX file")
    )

    tikz_options = parser.add_argument_group(_("TikZ options"))
    tikz_options.add_argument(
        "--standalone",
        action="store_true",
        help=_(
            "Create the header as a standalone to the tex file, "
            "enabling compilation of the result"
        ),
    )
    tikz_options.add_argument(
        "--pgfplotcompat",
        type=float,
        default=None,
        help=_("Set pgfplots backwards compatibility mode to given version"),
    )

    parser.add_argument(
        "--lang",
        "-l",
        choices=["en", "pt_BR"],
        default="en",
        help=_("Set language for plot"),
    )
    parser.add_argument(
        "--free-format",
        action="store_true",
        help=_("When parsing file handle all non `c` character as `d`"),
    )
    parser.add_argument(
        "--pdf-verbose", action="store_true", help=_("Print output of pdflatex")
    )
    parser.add_argument(
        "--black-and-white", action="store_true", help=_("Use only black color.")
    )
    parser.add_argument(
        "--background",
        help=_(
            "RGB values separated by commas for the background color "
            "of the plot. (Values in the 0,255 range)"
        ),
    )
    parser.add_argument(
        "--page-background",
        help=_(
            "RGB values separated by commas for the background color "
            "of the page. (Values in the 0,255 range)"
        ),
    )
    parser.add_argument(
        "--semilog",
        action="store_true",
        help=_("Use logarithmic scale for the x axis of the plot"),
    )
    parser.add_argument(
        "--success",
        type=str,
        default="c",
        help=_(
            "Flags that are interpreted as success, separated by commas.  Default: `c`"
        ),
    )
    parser.add_argument(
        "--maxtime",
        type=float,
        default=float("inf"),
        help=_(
            "Sets a maximum time for a solved problem. Any problem with a "
            "time greater than this will be considered failed."
        ),
    )
    parser.add_argument(
        "--mintime",
        type=float,
        default=0,
        help=_(
            "Sets a minimum time for a solved problem. Any problem with a "
            "time smaller than this will have the time set to this."
        ),
    )
    parser.add_argument(
        "--compare",
        choices=["exitflag", "optimalvalues"],
        default="exitflag",
        help=_("Choose the type of comparison to be made."),
    )
    parser.add_argument(
        "--unconstrained",
        action="store_true",
        help=_(
            "Set the problems to unconstrained, which implies that there "
            "is no primal feasibility to check."
        ),
    )
    parser.add_argument(
        "--infeasibility-tolerance",
        type=float,
        default=1e-4,
        help=_("Tolerance for the primal and dual infeasibilities"),
    )
    parser.add_argument(
        "--title",
        type=str,
        default=_("Performance Profile"),
        help=_("Set the title to be show on top of the performance profile"),
    )
    parser.add_argument("--no-title", action="store_true", help=_("Removes title"))
    parser.add_argument(
        "--xlabel",
        type=str,
        default=_("Performance ratio"),
        help=_("Set the x label of the performance profile"),
    )
    parser.add_argument(
        "--ylabel",
        type=str,
        default=_("Percentage of problems solved"),
        help=_("Set the y label of the performance profile"),
    )

    parser.add_argument("-c", "--cache", action="store_true", help=_("Enable cache."))
    parser.add_argument(
        "-s", "--subset", help=_("Name of a file with a subset of problems to compare")
    )
    parser.add_argument(
        "--tau", type=float, help=_("Limit the x-axis based this value")
    )
    parser.add_argument(
        "-f", "--force", action="store_true", help=_("Force overwrite the output file")
    )
    parser.add_argument(
        "-o",
        "--output",
        help=_("Name of the file to use as output (the correct extension will be add)"),
    )

    # Logging options
    logging_group = parser.add_argument_group(_("Logging options"))
    logging_group.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help=_("Enable verbose output (INFO level)"),
    )
    logging_group.add_argument(
        "--debug", action="store_true", help=_("Enable debug output (DEBUG level)")
    )
    logging_group.add_argument(
        "--log-file", help=_("Write log output to specified file")
    )

    parser.add_argument(
        "--demo", action="store_true", help=_("Use examples files as input")
    )
    parser.add_argument(
        "file_name",
        nargs="*",
        help=_(
            "The name of the files to be used for "
            "the performance profiling (for demo use `--demo`)"
        ),
    )

    parsed_args = parser.parse_args(args)

    # Set input files for demo
    if parsed_args.demo:
        if parsed_args.file_name:
            warnings.warn(_("Using demo mode. Ignoring input files."), UserWarning)
        parsed_args.file_name = [
            os.path.join(THIS_DIR, "examples/alpha.table"),
            os.path.join(THIS_DIR, "examples/beta.table"),
            os.path.join(THIS_DIR, "examples/gamma.table"),
        ]
    elif len(parsed_args.file_name) <= 1:
        raise ValueError(_("You must provide at least two input files."))

    return parsed_args

perprof.main.setup_logging(verbose=False, debug=False, log_file=None)

Configure logging for the application.

Parameters:

  • verbose (bool, default: False ) –

    Enable INFO level logging

  • debug (bool, default: False ) –

    Enable DEBUG level logging (overrides verbose)

  • log_file (str | None, default: None ) –

    Optional file path for logging output

Source code in perprof/main.py
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
def setup_logging(
    verbose: bool = False, debug: bool = False, log_file: str | None = None
) -> None:
    """Configure logging for the application.

    Args:
        verbose: Enable INFO level logging
        debug: Enable DEBUG level logging (overrides verbose)
        log_file: Optional file path for logging output
    """
    # Determine logging level
    if debug:
        level = logging.DEBUG
    elif verbose:
        level = logging.INFO
    else:
        level = logging.WARNING

    # Create formatter
    formatter = logging.Formatter("[%(levelname)s] %(name)s: %(message)s")

    # Configure root logger
    logger = logging.getLogger("perprof")
    logger.setLevel(level)

    # Remove existing handlers to avoid duplicates
    for handler in logger.handlers[:]:
        logger.removeHandler(handler)

    # Console handler
    console_handler = logging.StreamHandler(sys.stderr)
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)

    # File handler if specified
    if log_file:
        file_handler = logging.FileHandler(log_file)
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)

    # Prevent propagation to root logger
    logger.propagate = False

File parse

Functions to parse the files.

The files must be in the following format::

---
<Metadata 01>: <Value 01>
<Metadata 02>: <Value 02>
---
<Problem Name 01> <Exit Flag 01> <Cost 01>
<Problem Name 02> <Exit Flag 02> <Cost 02>
<Problem Name 03> <Exit Flag 03> <Cost 03>
...

Functions

perprof.parse.parse_file(filename, parser_options)

Parse one file.

Parameters:

  • filename (str) –

    name of the file to be parser

  • parser_options (dict) –

    dictionary with the following keys:

    • subset (list): list with the name of the problems to use
    • success (list): list with strings to mark sucess
    • mintime (float): minimum time running the solver
    • maxtime (float): maximum time running the solver
  • bool (free_format) –

    if False request that fail be mark with d

Returns:

  • data ( dict ) –

    performance profile data

  • algname ( str ) –

    name of the solver

Source code in perprof/parse.py
 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
def parse_file(filename, parser_options):
    """Parse one file.

    Args:
        filename (str): name of the file to be parser
        parser_options (dict):
            dictionary with the following keys:

            - subset (list): list with the name of the problems to use
            - success (list): list with strings to mark sucess
            - mintime (float): minimum time running the solver
            - maxtime (float): maximum time running the solver
        bool free_format: if False request that fail be mark with ``d``

    Returns:
        data (dict): performance profile data
        algname (str): name of the solver
    """
    options = parser_options.copy()
    options["algname"] = _str_sanitize(filename)
    colopts = ["name", "exit", "time", "fval", "primal", "dual"]
    col = {}
    for colopt in colopts:
        # Columns starts at 1 but indexing at 0
        options["col_" + colopt] = colopts.index(colopt) + 1
        col[colopt] = colopts.index(colopt)
    data = {}
    with open(filename, encoding="utf-8") as file_:
        line_number = 0
        in_yaml = False
        yaml_header = ""
        for line in file_:
            line_number += 1
            ldata = line.split()
            if len(ldata) == 0:
                continue  # Empty line
            # This is for backward compatibility
            if ldata[0] == "#Name" and len(ldata) >= 2:
                options["algname"] = _str_sanitize(ldata[1])
            # Handle YAML
            elif ldata[0] == "---":
                if in_yaml:
                    _parse_yaml(options, yaml_header)
                    for colopt in colopts:
                        # Columns starts at 1 but indexing at 0
                        col[colopt] = options["col_" + colopt] - 1
                    in_yaml = False
                else:
                    in_yaml = True
            elif in_yaml:
                yaml_header += line
            # Parse data
            elif len(ldata) < 2:
                raise ValueError(
                    _error_message(
                        filename,
                        line_number,
                        _("This line must have at least 2 elements."),
                    )
                )
            else:
                ldata[col["name"]] = _str_sanitize(ldata[col["name"]])
                pname = ldata[col["name"]]
                if options["subset"] and pname not in options["subset"]:
                    continue
                if pname in data:
                    raise ValueError(
                        _error_message(
                            filename,
                            line_number,
                            _("Duplicated problem: ") + pname + ".",
                        )
                    )
                try:
                    time = float(ldata[col["time"]])
                except Exception as exc:
                    raise ValueError(
                        _error_message(
                            filename,
                            line_number,
                            _("Problem has no time/cost: ") + pname + ".",
                        )
                    ) from exc
                if time < options["mintime"]:
                    time = options["mintime"]
                if time >= options["maxtime"]:
                    continue
                if parser_options["compare"] == "optimalvalues":
                    try:
                        if parser_options["unc"]:
                            primal = 0.0
                        else:
                            primal = float(ldata[col["primal"]])
                        dual = float(ldata[col["dual"]])
                    except Exception as exc:
                        raise ValueError(
                            _error_message(
                                filename,
                                line_number,
                                _("Column for primal or dual is out of bounds"),
                            )
                        ) from exc
                    if max(primal, dual) > parser_options["infeas_tol"]:
                        continue
                    data[pname] = {"time": time, "fval": float("inf")}
                    try:
                        data[pname]["fval"] = float(ldata[col["fval"]])
                    except Exception as exc:
                        raise ValueError(
                            _error_message(
                                filename,
                                line_number,
                                _("Column for fval is out of bounds"),
                            )
                        ) from exc
                elif parser_options["compare"] == "exitflag":
                    if time == 0:
                        raise ValueError(
                            _error_message(
                                filename, line_number, _("Time spending can't be zero.")
                            )
                        )
                    if ldata[col["exit"]] in options["success"]:
                        if len(ldata) < 3:
                            raise ValueError(
                                _error_message(
                                    filename,
                                    line_number,
                                    _("This line must have at least 3 elements."),
                                )
                            )
                        data[pname] = {"time": time, "fval": float("inf")}
                    elif options["free_format"] or ldata[col["exit"]] == "d":
                        data[pname] = {"time": float("inf"), "fval": float("inf")}
                    else:
                        raise ValueError(
                            _error_message(
                                filename,
                                line_number,
                                _(
                                    "The second element in this lime must be {} or d."
                                ).format(", ".join(options["success"])),
                            )
                        )
                else:
                    raise KeyError(
                        _(
                            "The parser option 'compare' should be "
                            "'exitflag' or 'optimalvalues'"
                        )
                    )

    if not data:
        raise ValueError(
            _("ERROR: List of problems (intersected with subset, if any) is empty")
        )
    return data, options["algname"]

Profile interface

The functions related with the perform (not the output).

Classes

perprof.prof.Pdata

Store data for performance profile.

Source code in perprof/prof.py
 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
class Pdata:
    """Store data for performance profile."""

    def __init__(self, parser_options, profiler_options):
        """Initialize Pdata.

        Args:
            parser_options (dict): parser configuration.
            profiler_options (dict): profiler configuration
        """
        self.data = load_data(parser_options)
        self.cache = profiler_options["cache"]
        self.force = profiler_options["force"]
        self.semilog = profiler_options["semilog"]
        self.black_and_white = profiler_options["black_and_white"]
        self.background = profiler_options["background"]
        self.page_background = profiler_options["page_background"]
        self.pdf_verbose = profiler_options["pdf_verbose"]
        self.output_format = profiler_options["output_format"]
        self.pgfplot_version = profiler_options["pgfplot_version"]
        self.tau = profiler_options["tau"]
        self.title = profiler_options["title"]
        self.xlabel = profiler_options["xlabel"]
        self.ylabel = profiler_options["ylabel"]
        self.already_scaled = False
        self.tablename = profiler_options["output"]

        self.solvers = sorted(list(self.data.keys()))
        self.problems = {x for v in self.data.values() for x in v}
        self.number_problems = len(self.problems)

    def __repr__(self):
        """Return a representation of the Pdata object."""
        str2output = " " * 18

        for solver in self.solvers:
            str2output += f"{solver[-16:]:>16}  "
        str2output += "\n"

        for problem in self.problems:
            str2output += f"{problem:>16}  "
            for solver in self.solvers:
                try:
                    str2output += " " * 8 + f"{self.data[solver][problem]['time']:8.4} "
                except KeyError:
                    str2output += " " * 13 + "inf  "
            str2output += "\n"

        return str2output[:-2]

    def get_set_solvers(self):
        """Get the set of solvers to use.

        Returns:
            solvers (list[dict]): list of solvers
        """
        return self.solvers

    def get_set_problems(self):
        """Get the set of problems to use.

        Returns:
            problems (list[str]): list of problems
        """
        return self.problems

    def scale(self):
        """Scale time."""
        times_set = set()
        for problem in self.problems:
            for solver in self.solvers:
                try:
                    self.data[solver][problem]["time"]
                except (KeyError, TypeError):
                    self.data[solver][problem] = {
                        "time": float("inf"),
                        "fval": float("inf"),
                    }

            min_fval = min(v[problem]["fval"] for v in self.data.values())
            if min_fval < float("inf"):
                min_time = min(
                    v[problem]["time"]
                    for v in self.data.values()
                    if v[problem]["fval"] < min_fval + abs(min_fval) * 1e-3 + 1e-6
                )
            else:
                min_time = min(v[problem]["time"] for v in self.data.values())

            for solver in self.solvers:
                try:
                    self.data[solver][problem]["time"] = (
                        self.data[solver][problem]["time"] / min_time
                    )
                except ZeroDivisionError:
                    self.data[solver][problem] = {"time": float("inf")}
                if self.data[solver][problem]["time"] < float("inf"):
                    times_set.add(self.data[solver][problem]["time"])
        if not times_set:
            raise ValueError(_("ERROR: problem set is empty"))

        self.times = sorted(times_set)
        maxt = self.times[-1]
        self.times.append(maxt * 1.05)

        self.already_scaled = True

    def set_percent_problems_solved_by_time(self):
        """Set the percent of problems solved by time."""
        # ppsbt = Percent Problems Solved By Time
        self.ppsbt = {}
        for solver in self.solvers:
            self.ppsbt[solver] = []
            for time in self.times:
                aux = 0
                for problem in self.problems:
                    if time >= self.data[solver][problem]["time"]:
                        aux += 1
                self.ppsbt[solver].append(aux / self.number_problems)
            if self.ppsbt[solver][-1] == 0:
                raise ValueError(
                    _("ERROR:")
                    + solver
                    + _(" has no solved problems. Verify the 'success' flag.")
                )

    def pre_plot(self):
        """Run plot-related checks and processing."""
        if self.force and self.output != sys.stdout and os.path.exists(self.output):
            raise ValueError(
                _("ERROR: File {} exists.\nUse `-f` to overwrite").format(self.output)
            )

        if not self.already_scaled:
            self.scale()

        try:
            self.ppsbt
        except AttributeError:
            self.set_percent_problems_solved_by_time()

    def plot(self):
        """Generate the plot."""
        raise NotImplementedError()

    def print_rob_eff_table(self):
        """Print table of robustness and efficiency."""
        if not self.already_scaled:
            self.scale()

        try:
            self.ppsbt
        except AttributeError:
            self.set_percent_problems_solved_by_time()

        if self.tablename is None:
            output = sys.stdout
            print("Solvers    | Robust  | Effic")
            for solver in self.solvers:
                robustness = round(100 * self.ppsbt[solver][-1], 3)
                efficiency = round(100 * self.ppsbt[solver][0], 3)
                print(f"{solver:10} | {robustness:6.3f}% | {efficiency:6.3f}%")
        else:
            output = f"{self.tablename}.tex"
            output = os.path.abspath(output)

            str2output = [
                "\\begin{tabular}{|c|r|r|} \\hline",
                "Solver & Robustness & Efficiency \\\\ \\hline",
            ]
            for solver in self.solvers:
                robustness = round(100 * self.ppsbt[solver][-1], 3)
                efficiency = round(100 * self.ppsbt[solver][0], 3)
                str2output.append(
                    f"{solver} & {robustness} \\% & {efficiency} \\% \\\\ \\hline"
                )
            str2output.append("\\end{tabular}")

            with open(output, "w", encoding="utf-8") as file_:
                file_.write("\n".join(str2output))
Functions
__init__(parser_options, profiler_options)

Parameters:

  • parser_options (dict) –

    parser configuration.

  • profiler_options (dict) –

    profiler configuration

Source code in perprof/prof.py
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
def __init__(self, parser_options, profiler_options):
    """Initialize Pdata.

    Args:
        parser_options (dict): parser configuration.
        profiler_options (dict): profiler configuration
    """
    self.data = load_data(parser_options)
    self.cache = profiler_options["cache"]
    self.force = profiler_options["force"]
    self.semilog = profiler_options["semilog"]
    self.black_and_white = profiler_options["black_and_white"]
    self.background = profiler_options["background"]
    self.page_background = profiler_options["page_background"]
    self.pdf_verbose = profiler_options["pdf_verbose"]
    self.output_format = profiler_options["output_format"]
    self.pgfplot_version = profiler_options["pgfplot_version"]
    self.tau = profiler_options["tau"]
    self.title = profiler_options["title"]
    self.xlabel = profiler_options["xlabel"]
    self.ylabel = profiler_options["ylabel"]
    self.already_scaled = False
    self.tablename = profiler_options["output"]

    self.solvers = sorted(list(self.data.keys()))
    self.problems = {x for v in self.data.values() for x in v}
    self.number_problems = len(self.problems)
__repr__()

Return a representation of the Pdata object.

Source code in perprof/prof.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def __repr__(self):
    """Return a representation of the Pdata object."""
    str2output = " " * 18

    for solver in self.solvers:
        str2output += f"{solver[-16:]:>16}  "
    str2output += "\n"

    for problem in self.problems:
        str2output += f"{problem:>16}  "
        for solver in self.solvers:
            try:
                str2output += " " * 8 + f"{self.data[solver][problem]['time']:8.4} "
            except KeyError:
                str2output += " " * 13 + "inf  "
        str2output += "\n"

    return str2output[:-2]
get_set_problems()

Get the set of problems to use.

Returns:

  • problems ( list[str] ) –

    list of problems

Source code in perprof/prof.py
85
86
87
88
89
90
91
def get_set_problems(self):
    """Get the set of problems to use.

    Returns:
        problems (list[str]): list of problems
    """
    return self.problems
get_set_solvers()

Get the set of solvers to use.

Returns:

  • solvers ( list[dict] ) –

    list of solvers

Source code in perprof/prof.py
77
78
79
80
81
82
83
def get_set_solvers(self):
    """Get the set of solvers to use.

    Returns:
        solvers (list[dict]): list of solvers
    """
    return self.solvers
plot()

Generate the plot.

Source code in perprof/prof.py
168
169
170
def plot(self):
    """Generate the plot."""
    raise NotImplementedError()
pre_plot()

Run plot-related checks and processing.

Source code in perprof/prof.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def pre_plot(self):
    """Run plot-related checks and processing."""
    if self.force and self.output != sys.stdout and os.path.exists(self.output):
        raise ValueError(
            _("ERROR: File {} exists.\nUse `-f` to overwrite").format(self.output)
        )

    if not self.already_scaled:
        self.scale()

    try:
        self.ppsbt
    except AttributeError:
        self.set_percent_problems_solved_by_time()
print_rob_eff_table()

Print table of robustness and efficiency.

Source code in perprof/prof.py
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
def print_rob_eff_table(self):
    """Print table of robustness and efficiency."""
    if not self.already_scaled:
        self.scale()

    try:
        self.ppsbt
    except AttributeError:
        self.set_percent_problems_solved_by_time()

    if self.tablename is None:
        output = sys.stdout
        print("Solvers    | Robust  | Effic")
        for solver in self.solvers:
            robustness = round(100 * self.ppsbt[solver][-1], 3)
            efficiency = round(100 * self.ppsbt[solver][0], 3)
            print(f"{solver:10} | {robustness:6.3f}% | {efficiency:6.3f}%")
    else:
        output = f"{self.tablename}.tex"
        output = os.path.abspath(output)

        str2output = [
            "\\begin{tabular}{|c|r|r|} \\hline",
            "Solver & Robustness & Efficiency \\\\ \\hline",
        ]
        for solver in self.solvers:
            robustness = round(100 * self.ppsbt[solver][-1], 3)
            efficiency = round(100 * self.ppsbt[solver][0], 3)
            str2output.append(
                f"{solver} & {robustness} \\% & {efficiency} \\% \\\\ \\hline"
            )
        str2output.append("\\end{tabular}")

        with open(output, "w", encoding="utf-8") as file_:
            file_.write("\n".join(str2output))
scale()

Scale time.

Source code in perprof/prof.py
 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
def scale(self):
    """Scale time."""
    times_set = set()
    for problem in self.problems:
        for solver in self.solvers:
            try:
                self.data[solver][problem]["time"]
            except (KeyError, TypeError):
                self.data[solver][problem] = {
                    "time": float("inf"),
                    "fval": float("inf"),
                }

        min_fval = min(v[problem]["fval"] for v in self.data.values())
        if min_fval < float("inf"):
            min_time = min(
                v[problem]["time"]
                for v in self.data.values()
                if v[problem]["fval"] < min_fval + abs(min_fval) * 1e-3 + 1e-6
            )
        else:
            min_time = min(v[problem]["time"] for v in self.data.values())

        for solver in self.solvers:
            try:
                self.data[solver][problem]["time"] = (
                    self.data[solver][problem]["time"] / min_time
                )
            except ZeroDivisionError:
                self.data[solver][problem] = {"time": float("inf")}
            if self.data[solver][problem]["time"] < float("inf"):
                times_set.add(self.data[solver][problem]["time"])
    if not times_set:
        raise ValueError(_("ERROR: problem set is empty"))

    self.times = sorted(times_set)
    maxt = self.times[-1]
    self.times.append(maxt * 1.05)

    self.already_scaled = True
set_percent_problems_solved_by_time()

Set the percent of problems solved by time.

Source code in perprof/prof.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def set_percent_problems_solved_by_time(self):
    """Set the percent of problems solved by time."""
    # ppsbt = Percent Problems Solved By Time
    self.ppsbt = {}
    for solver in self.solvers:
        self.ppsbt[solver] = []
        for time in self.times:
            aux = 0
            for problem in self.problems:
                if time >= self.data[solver][problem]["time"]:
                    aux += 1
            self.ppsbt[solver].append(aux / self.number_problems)
        if self.ppsbt[solver][-1] == 0:
            raise ValueError(
                _("ERROR:")
                + solver
                + _(" has no solved problems. Verify the 'success' flag.")
            )

Functions

perprof.prof.load_data(parser_options)

Load the data.

Parameters:

  • parser_options (dict) –

    the configuration dicionary

Source code in perprof/prof.py
14
15
16
17
18
19
20
21
22
23
24
def load_data(parser_options):
    """Load the data.

    Args:
        parser_options (dict): the configuration dicionary
    """
    data = {}
    for file_ in parser_options["files"]:
        data_tmp, solver_name = parse.parse_file(file_, parser_options)
        data[solver_name] = data_tmp
    return data

Profiler using TikZ

Plot using tikz.

Classes

perprof.tikz.Profiler

Bases: Pdata

Performance profile generator using TikZ/PGFPlots for LaTeX integration.

Creates LaTeX-based performance profile plots using TikZ and PGFPlots. Suitable for academic publications and technical reports.

Features: - Vector graphics with scalability - LaTeX document font consistency - Mathematical notation support - Standalone and embedded figure support

The profiler generates either standalone LaTeX documents or TikZ code that can be embedded into larger documents. It supports both color and black-and-white output with extensive line style and color customization.

Attributes:

  • output (str | TextIO) –

    Output filename or file handle (stdout for None).

  • standalone (bool) –

    Whether to generate standalone LaTeX document.

  • output_format (str) –

    Target format (“tex” or “pdf”).

  • plot_lang (function) –

    Localization function for plot text.

Example

Creating a TikZ profiler for publication-quality output:

from perprof.tikz import Profiler

parser_opts = {
    "files": ["solver1.txt", "solver2.txt"],
    "success": ["converged"],
    "free_format": True
}
profiler_opts = {
    "output": "publication_figure",
    "output_format": "pdf",
    "standalone": True,
    "black_and_white": False,
    "title": "Performance Profile Comparison",
    "lang": "en"
}
profiler = Profiler(parser_opts, profiler_opts)
profiler.plot()  # Creates publication_figure.tex and compiles to PDF
Source code in perprof/tikz.py
 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
class Profiler(prof.Pdata):
    """Performance profile generator using TikZ/PGFPlots for LaTeX integration.

    Creates LaTeX-based performance profile plots using TikZ and PGFPlots.
    Suitable for academic publications and technical reports.

    Features:
    - Vector graphics with scalability
    - LaTeX document font consistency
    - Mathematical notation support
    - Standalone and embedded figure support

    The profiler generates either standalone LaTeX documents or TikZ code that
    can be embedded into larger documents. It supports both color and black-and-white
    output with extensive line style and color customization.

    Attributes:
        output (str|TextIO): Output filename or file handle (stdout for None).
        standalone (bool): Whether to generate standalone LaTeX document.
        output_format (str): Target format ("tex" or "pdf").
        plot_lang (function): Localization function for plot text.

    Example:
        Creating a TikZ profiler for publication-quality output:

        ```python
        from perprof.tikz import Profiler

        parser_opts = {
            "files": ["solver1.txt", "solver2.txt"],
            "success": ["converged"],
            "free_format": True
        }
        profiler_opts = {
            "output": "publication_figure",
            "output_format": "pdf",
            "standalone": True,
            "black_and_white": False,
            "title": "Performance Profile Comparison",
            "lang": "en"
        }
        profiler = Profiler(parser_opts, profiler_opts)
        profiler.plot()  # Creates publication_figure.tex and compiles to PDF
        ```
    """

    def __init__(self, parser_options, profiler_options):
        """Initialize TikZ/PGFPlots-based performance profiler.

        Args:
            parser_options (dict): Data parsing configuration including:
                - files: List of solver data files to process
                - success: Success criteria for solver convergence
                - maxtime/mintime: Time filtering bounds
                - compare: Performance metric to compare (default: "time")
                - subset: Optional problem subset restriction
            profiler_options (dict): Visualization configuration including:
                - output: Output filename prefix (None for stdout)
                - output_format: Target format ("tex" or "pdf")
                - standalone: Generate standalone LaTeX document
                - black_and_white: Use monochrome line styles
                - semilog: Use logarithmic x-axis scaling
                - lang: Language for plot labels
                - title/xlabel/ylabel: Plot text customization
                - pgfplot_version: PGFPlots compatibility version
                - background/page_background: Color customization

        Note:
            TikZ backend has solver limits: 13 for black-and-white plots,
            30 for color plots. This is due to the finite number of
            predefined line styles and colors in the TikZ implementation.

        Example:
            ```python
            parser_opts = {"files": ["data1.txt"], "success": ["converged"]}
            profiler_opts = {"output": "plot", "output_format": "pdf",
                           "standalone": True, "lang": "en"}
            profiler = Profiler(parser_opts, profiler_opts)
            ```
        """
        if profiler_options["output"] is None:
            self.output = sys.stdout
        else:
            self.output = f"{profiler_options['output']}.tex"
            self.output = os.path.abspath(self.output)
        self.standalone = profiler_options["standalone"]
        self.output_format = profiler_options["output_format"]

        # Language for the plot
        translation = gettext.translation(
            "perprof", os.path.join(THIS_DIR, "locale"), [profiler_options["lang"]]
        )
        self.plot_lang = translation.gettext

        prof.Pdata.__init__(self, parser_options, profiler_options)

    # pylint: disable=too-many-locals,too-many-branches,too-many-statements
    def plot(self):
        r"""Generate and save the TikZ/PGFPlots performance profile.

        Creates LaTeX code for performance profiles using TikZ and PGFPlots.
        Output can be standalone LaTeX document or TikZ code for embedding.

        Process:
        1. Document structure (if standalone)
        2. Color and line style definitions
        3. PGFPlots axis environment with scaling and labels
        4. Data plots using step functions
        5. Legend and finalization
        6. Optional PDF compilation

        Features:
        - Step plots using PGFPlots \\addplot commands
        - Logarithmic x-axis scaling (if semilog=True)
        - Color and line style combinations
        - Automatic PDF compilation for PDF output
        - Background color support

        Validates solver count limits (13 for B&W, 30 for color).

        Raises:
            ValueError: If too many solvers for the chosen style (B&W vs color).
            subprocess-related exceptions: If PDF compilation fails.

        Example:
            ```python
            # After creating profiler instance (see __init__ example)
            profiler.plot()  # Creates .tex file and optionally compiles to PDF
            ```

        """
        self.pre_plot()

        if self.black_and_white and len(self.solvers) > 13:
            raise ValueError(
                _("ERROR: Maximum numbers of solvers in black and white plot is 13.")
            )
        if not self.black_and_white and len(self.solvers) > 30:
            raise ValueError(
                _("ERROR: Maximum numbers of solvers in color plot is 30.")
            )

        maxt = max(self.times)
        try:
            maxt = min(maxt, self.tau)
        except (AttributeError, TypeError):
            self.tau = maxt

        str2output = []

        if self.standalone or self.output_format == "pdf":
            str2output.append("\\documentclass{standalone}")
            str2output.append("\\usepackage[utf8]{inputenc}")
            str2output.append("\\usepackage[T1]{fontenc}")
            str2output.append("\\usepackage{tikz}")
            str2output.append("\\usepackage{pgfplots}")
            if self.pgfplot_version is not None:
                str2output.append(f"\\pgfplotsset{{compat={self.pgfplot_version}}}")
            else:
                str2output.append(
                    "\\pgfplotsset{compat=newest,compat/show suggested version=false}"
                )
            if self.page_background:
                str2output.append(
                    "\\definecolor{pagebg}{RGB}{"
                    + ",".join(str(x) for x in self.page_background)
                    + "}"
                )
                str2output.append("\\pagecolor{pagebg}")
            str2output.append("\\begin{document}")
        else:
            str2output.append("\\begin{center}")
        str2output.append("\\begin{tikzpicture}")

        if self.semilog:
            str2output.append("  \\begin{semilogxaxis}[const plot,")
        else:
            str2output.append("  \\begin{axis}[const plot,")
        if self.black_and_white:
            lines = ["dashed", "dotted", "dashdotted", "dashdotdotted"]
            types = ["", "loosely ", "densely "]
            str2output.append("  cycle list={")
            aux = ["solid"]
            for k in range(0, len(self.solvers)):
                i = k % len(lines)
                j = k // len(lines)
                aux.append(f"  {{{types[j]}{lines[i]}}}")
            str2output.append(",\n".join(aux) + "},")
        else:
            colors = [
                "blue",
                "red",
                "black",
                "brown",
                "green!80!black",
                "magenta!80!black",
            ]
            lines = ["solid", "dashed", "dotted", "dashdotted", "dashdotdotted"]
            str2output.append("  cycle list={")
            aux = []
            for k in range(0, len(self.solvers)):
                i = k % len(colors)
                j = k // len(colors)
                aux.append(f"  {{{colors[i]},{lines[j]}}}")
            str2output.append(",\n".join(aux) + "},")
        if self.background:
            red, green, blue = self.background
            str2output.append(
                "axis background/.style="
                f"{{fill={{rgb,255:red,{red};green,{green};blue,{blue}}}}},"
            )
        if len(self.solvers) > 5:
            legend_pos = "outer north east"
        else:
            legend_pos = "south east"
        if self.title is None:
            title = ""
        else:
            title = f"title={{{self.plot_lang(self.title)}}},"

        xlabel = self.plot_lang(self.xlabel)
        ylabel = self.plot_lang(self.ylabel)

        str2output.append(
            f"""xmin=1, xmax={maxt:.2f},
                ymin=-0.003, ymax=1.003,
                ymajorgrids,
                ytick={{0,0.2,0.4,0.6,0.8,1.0}},
                xlabel={{{xlabel}}},
                ylabel={{{ylabel}}},{title}
                legend pos={{{legend_pos}}},
                width=\\textwidth
            ]"""
        )

        for solver in self.solvers:
            this_ppsbt = self.ppsbt[solver]
            str2output.append("  \\addplot+[mark=none, thick] coordinates {")
            str2output.append(f"    ({self.times[0]:.4f},{this_ppsbt[0]:.4f})")
            last_t = round(self.times[0], 4)
            last_p = round(self.ppsbt[solver][0], 4)
            for i in range(1, len(self.times) - 1):
                dx = round(self.times[i], 4) - last_t
                dx2 = round(self.times[i + 1], 4) - last_t
                dy = round(this_ppsbt[i], 4) - last_p
                dy2 = round(this_ppsbt[i + 1], 4) - last_p
                if dx * dy2 == dy * dx2:
                    continue
                if self.times[i] <= self.tau:
                    time = round(self.times[i], 4)
                    ppsbt = round(self.ppsbt[solver][i], 4)
                    str2output.append(f"    ({time:.4f},{ppsbt:.4f})")
                    last_t = time
                    last_p = ppsbt
                else:
                    break
            j = len(self.times) - 1
            str2output.append(f"    ({self.times[j]:.4f},{this_ppsbt[j]:.4f})")
            str2output.append("  };")
            str2output.append(f"  \\addlegendentry{{{solver}}}")

        if self.semilog:
            str2output.append("  \\end{semilogxaxis}")
        else:
            str2output.append("  \\end{axis}")
        str2output.append("\\end{tikzpicture}")
        if self.standalone or self.output_format == "pdf":
            str2output.append("\\end{document}")
        else:
            str2output.append("\\end{center}\n")

        try:
            with open(self.output, "w", encoding="utf-8") as file_:
                file_.write("\n".join(str2output))

            if self.output_format == "pdf":
                if self.pdf_verbose:
                    mode = "nonstopmode"
                else:
                    mode = "batchmode"
                subprocess.check_call(
                    [
                        "pdflatex",
                        "-interaction",
                        mode,
                        "-output-directory",
                        os.path.dirname(self.output),
                        self.output,
                    ]
                )
        except TypeError:
            # When using stdout
            print(str2output, file=self.output)
Functions
__init__(parser_options, profiler_options)

Parameters:

  • parser_options (dict) –

    Data parsing configuration including: - files: List of solver data files to process - success: Success criteria for solver convergence - maxtime/mintime: Time filtering bounds - compare: Performance metric to compare (default: “time”) - subset: Optional problem subset restriction

  • profiler_options (dict) –

    Visualization configuration including: - output: Output filename prefix (None for stdout) - output_format: Target format (“tex” or “pdf”) - standalone: Generate standalone LaTeX document - black_and_white: Use monochrome line styles - semilog: Use logarithmic x-axis scaling - lang: Language for plot labels - title/xlabel/ylabel: Plot text customization - pgfplot_version: PGFPlots compatibility version - background/page_background: Color customization

Note

TikZ backend has solver limits: 13 for black-and-white plots, 30 for color plots. This is due to the finite number of predefined line styles and colors in the TikZ implementation.

Example
parser_opts = {"files": ["data1.txt"], "success": ["converged"]}
profiler_opts = {"output": "plot", "output_format": "pdf",
               "standalone": True, "lang": "en"}
profiler = Profiler(parser_opts, profiler_opts)
Source code in perprof/tikz.py
 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
def __init__(self, parser_options, profiler_options):
    """Initialize TikZ/PGFPlots-based performance profiler.

    Args:
        parser_options (dict): Data parsing configuration including:
            - files: List of solver data files to process
            - success: Success criteria for solver convergence
            - maxtime/mintime: Time filtering bounds
            - compare: Performance metric to compare (default: "time")
            - subset: Optional problem subset restriction
        profiler_options (dict): Visualization configuration including:
            - output: Output filename prefix (None for stdout)
            - output_format: Target format ("tex" or "pdf")
            - standalone: Generate standalone LaTeX document
            - black_and_white: Use monochrome line styles
            - semilog: Use logarithmic x-axis scaling
            - lang: Language for plot labels
            - title/xlabel/ylabel: Plot text customization
            - pgfplot_version: PGFPlots compatibility version
            - background/page_background: Color customization

    Note:
        TikZ backend has solver limits: 13 for black-and-white plots,
        30 for color plots. This is due to the finite number of
        predefined line styles and colors in the TikZ implementation.

    Example:
        ```python
        parser_opts = {"files": ["data1.txt"], "success": ["converged"]}
        profiler_opts = {"output": "plot", "output_format": "pdf",
                       "standalone": True, "lang": "en"}
        profiler = Profiler(parser_opts, profiler_opts)
        ```
    """
    if profiler_options["output"] is None:
        self.output = sys.stdout
    else:
        self.output = f"{profiler_options['output']}.tex"
        self.output = os.path.abspath(self.output)
    self.standalone = profiler_options["standalone"]
    self.output_format = profiler_options["output_format"]

    # Language for the plot
    translation = gettext.translation(
        "perprof", os.path.join(THIS_DIR, "locale"), [profiler_options["lang"]]
    )
    self.plot_lang = translation.gettext

    prof.Pdata.__init__(self, parser_options, profiler_options)
plot()

Generate and save the TikZ/PGFPlots performance profile.

Creates LaTeX code for performance profiles using TikZ and PGFPlots. Output can be standalone LaTeX document or TikZ code for embedding.

Process: 1. Document structure (if standalone) 2. Color and line style definitions 3. PGFPlots axis environment with scaling and labels 4. Data plots using step functions 5. Legend and finalization 6. Optional PDF compilation

Features: - Step plots using PGFPlots \addplot commands - Logarithmic x-axis scaling (if semilog=True) - Color and line style combinations - Automatic PDF compilation for PDF output - Background color support

Validates solver count limits (13 for B&W, 30 for color).

Raises:

  • ValueError –

    If too many solvers for the chosen style (B&W vs color).

  • subprocess-related exceptions –

    If PDF compilation fails.

Example
# After creating profiler instance (see __init__ example)
profiler.plot()  # Creates .tex file and optionally compiles to PDF
Source code in perprof/tikz.py
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
def plot(self):
    r"""Generate and save the TikZ/PGFPlots performance profile.

    Creates LaTeX code for performance profiles using TikZ and PGFPlots.
    Output can be standalone LaTeX document or TikZ code for embedding.

    Process:
    1. Document structure (if standalone)
    2. Color and line style definitions
    3. PGFPlots axis environment with scaling and labels
    4. Data plots using step functions
    5. Legend and finalization
    6. Optional PDF compilation

    Features:
    - Step plots using PGFPlots \\addplot commands
    - Logarithmic x-axis scaling (if semilog=True)
    - Color and line style combinations
    - Automatic PDF compilation for PDF output
    - Background color support

    Validates solver count limits (13 for B&W, 30 for color).

    Raises:
        ValueError: If too many solvers for the chosen style (B&W vs color).
        subprocess-related exceptions: If PDF compilation fails.

    Example:
        ```python
        # After creating profiler instance (see __init__ example)
        profiler.plot()  # Creates .tex file and optionally compiles to PDF
        ```

    """
    self.pre_plot()

    if self.black_and_white and len(self.solvers) > 13:
        raise ValueError(
            _("ERROR: Maximum numbers of solvers in black and white plot is 13.")
        )
    if not self.black_and_white and len(self.solvers) > 30:
        raise ValueError(
            _("ERROR: Maximum numbers of solvers in color plot is 30.")
        )

    maxt = max(self.times)
    try:
        maxt = min(maxt, self.tau)
    except (AttributeError, TypeError):
        self.tau = maxt

    str2output = []

    if self.standalone or self.output_format == "pdf":
        str2output.append("\\documentclass{standalone}")
        str2output.append("\\usepackage[utf8]{inputenc}")
        str2output.append("\\usepackage[T1]{fontenc}")
        str2output.append("\\usepackage{tikz}")
        str2output.append("\\usepackage{pgfplots}")
        if self.pgfplot_version is not None:
            str2output.append(f"\\pgfplotsset{{compat={self.pgfplot_version}}}")
        else:
            str2output.append(
                "\\pgfplotsset{compat=newest,compat/show suggested version=false}"
            )
        if self.page_background:
            str2output.append(
                "\\definecolor{pagebg}{RGB}{"
                + ",".join(str(x) for x in self.page_background)
                + "}"
            )
            str2output.append("\\pagecolor{pagebg}")
        str2output.append("\\begin{document}")
    else:
        str2output.append("\\begin{center}")
    str2output.append("\\begin{tikzpicture}")

    if self.semilog:
        str2output.append("  \\begin{semilogxaxis}[const plot,")
    else:
        str2output.append("  \\begin{axis}[const plot,")
    if self.black_and_white:
        lines = ["dashed", "dotted", "dashdotted", "dashdotdotted"]
        types = ["", "loosely ", "densely "]
        str2output.append("  cycle list={")
        aux = ["solid"]
        for k in range(0, len(self.solvers)):
            i = k % len(lines)
            j = k // len(lines)
            aux.append(f"  {{{types[j]}{lines[i]}}}")
        str2output.append(",\n".join(aux) + "},")
    else:
        colors = [
            "blue",
            "red",
            "black",
            "brown",
            "green!80!black",
            "magenta!80!black",
        ]
        lines = ["solid", "dashed", "dotted", "dashdotted", "dashdotdotted"]
        str2output.append("  cycle list={")
        aux = []
        for k in range(0, len(self.solvers)):
            i = k % len(colors)
            j = k // len(colors)
            aux.append(f"  {{{colors[i]},{lines[j]}}}")
        str2output.append(",\n".join(aux) + "},")
    if self.background:
        red, green, blue = self.background
        str2output.append(
            "axis background/.style="
            f"{{fill={{rgb,255:red,{red};green,{green};blue,{blue}}}}},"
        )
    if len(self.solvers) > 5:
        legend_pos = "outer north east"
    else:
        legend_pos = "south east"
    if self.title is None:
        title = ""
    else:
        title = f"title={{{self.plot_lang(self.title)}}},"

    xlabel = self.plot_lang(self.xlabel)
    ylabel = self.plot_lang(self.ylabel)

    str2output.append(
        f"""xmin=1, xmax={maxt:.2f},
            ymin=-0.003, ymax=1.003,
            ymajorgrids,
            ytick={{0,0.2,0.4,0.6,0.8,1.0}},
            xlabel={{{xlabel}}},
            ylabel={{{ylabel}}},{title}
            legend pos={{{legend_pos}}},
            width=\\textwidth
        ]"""
    )

    for solver in self.solvers:
        this_ppsbt = self.ppsbt[solver]
        str2output.append("  \\addplot+[mark=none, thick] coordinates {")
        str2output.append(f"    ({self.times[0]:.4f},{this_ppsbt[0]:.4f})")
        last_t = round(self.times[0], 4)
        last_p = round(self.ppsbt[solver][0], 4)
        for i in range(1, len(self.times) - 1):
            dx = round(self.times[i], 4) - last_t
            dx2 = round(self.times[i + 1], 4) - last_t
            dy = round(this_ppsbt[i], 4) - last_p
            dy2 = round(this_ppsbt[i + 1], 4) - last_p
            if dx * dy2 == dy * dx2:
                continue
            if self.times[i] <= self.tau:
                time = round(self.times[i], 4)
                ppsbt = round(self.ppsbt[solver][i], 4)
                str2output.append(f"    ({time:.4f},{ppsbt:.4f})")
                last_t = time
                last_p = ppsbt
            else:
                break
        j = len(self.times) - 1
        str2output.append(f"    ({self.times[j]:.4f},{this_ppsbt[j]:.4f})")
        str2output.append("  };")
        str2output.append(f"  \\addlegendentry{{{solver}}}")

    if self.semilog:
        str2output.append("  \\end{semilogxaxis}")
    else:
        str2output.append("  \\end{axis}")
    str2output.append("\\end{tikzpicture}")
    if self.standalone or self.output_format == "pdf":
        str2output.append("\\end{document}")
    else:
        str2output.append("\\end{center}\n")

    try:
        with open(self.output, "w", encoding="utf-8") as file_:
            file_.write("\n".join(str2output))

        if self.output_format == "pdf":
            if self.pdf_verbose:
                mode = "nonstopmode"
            else:
                mode = "batchmode"
            subprocess.check_call(
                [
                    "pdflatex",
                    "-interaction",
                    mode,
                    "-output-directory",
                    os.path.dirname(self.output),
                    self.output,
                ]
            )
    except TypeError:
        # When using stdout
        print(str2output, file=self.output)

Profiler using matplotlib

Plot using matplotlib.

Classes

perprof.matplotlib.Profiler

Bases: Pdata

Performance profile generator using matplotlib backend.

Creates performance profile plots using matplotlib with support for multiple output formats (PNG, PDF, SVG, EPS, PS) and customization options.

Generates step plots showing cumulative distribution of solver performance ratios.

Attributes:

  • output (str) –

    Output filename including format extension.

  • output_format (str) –

    File format for the output (png, pdf, svg, etc.).

  • plot_lang (function) –

    Localization function for plot text.

Example

Creating a matplotlib profiler for performance analysis:

from perprof.matplotlib import Profiler

# Configure data parsing
parser_opts = {
    "files": ["solver1.txt", "solver2.txt"],
    "success": ["converged", "solved"],
    "maxtime": 300.0,
    "compare": "time"
}

# Configure plot appearance
profiler_opts = {
    "output": "comparison",
    "output_format": "png",
    "semilog": True,
    "lang": "en",
    "title": "Performance Comparison"
}

# Generate plot
profiler = Profiler(parser_opts, profiler_opts)
profiler.plot()  # Creates comparison.png
Source code in perprof/matplotlib.py
 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
class Profiler(prof.Pdata):
    """Performance profile generator using matplotlib backend.

    Creates performance profile plots using matplotlib with support for multiple
    output formats (PNG, PDF, SVG, EPS, PS) and customization options.

    Generates step plots showing cumulative distribution of solver performance ratios.

    Attributes:
        output (str): Output filename including format extension.
        output_format (str): File format for the output (png, pdf, svg, etc.).
        plot_lang (function): Localization function for plot text.

    Example:
        Creating a matplotlib profiler for performance analysis:

        ```python
        from perprof.matplotlib import Profiler

        # Configure data parsing
        parser_opts = {
            "files": ["solver1.txt", "solver2.txt"],
            "success": ["converged", "solved"],
            "maxtime": 300.0,
            "compare": "time"
        }

        # Configure plot appearance
        profiler_opts = {
            "output": "comparison",
            "output_format": "png",
            "semilog": True,
            "lang": "en",
            "title": "Performance Comparison"
        }

        # Generate plot
        profiler = Profiler(parser_opts, profiler_opts)
        profiler.plot()  # Creates comparison.png
        ```
    """

    def __init__(self, parser_options, profiler_options):
        """Initialize matplotlib-based performance profiler.

        Args:
            parser_options (dict): Data parsing configuration including:
                - files: List of solver data files to process
                - success: Success criteria for solver convergence
                - maxtime/mintime: Time filtering bounds
                - compare: Performance metric to compare (default: "time")
                - subset: Optional problem subset restriction
            profiler_options (dict): Visualization configuration including:
                - output: Output filename prefix (extension auto-added)
                - output_format: File format (png, pdf, svg, eps, ps)
                - semilog: Use logarithmic x-axis scaling
                - black_and_white: Use monochrome line styles
                - lang: Language for plot labels
                - title/xlabel/ylabel: Plot text customization
                - background/page_background: Color customization

        Example:
            ```python
            parser_opts = {"files": ["data1.txt"], "success": ["converged"]}
            profiler_opts = {"output": "plot", "output_format": "png", "lang": "en"}
            profiler = Profiler(parser_opts, profiler_opts)
            ```
        """
        if profiler_options["output"] is None:
            self.output = f"performance-profile.{profiler_options['output_format']}"
        else:
            self.output = (
                f"{profiler_options['output']}.{profiler_options['output_format']}"
            )
        self.output_format = profiler_options["output_format"]

        # Language for the plot
        translation = gettext.translation(
            "perprof", os.path.join(THIS_DIR, "locale"), [profiler_options["lang"]]
        )
        self.plot_lang = translation.gettext

        prof.Pdata.__init__(self, parser_options, profiler_options)

    # pylint: disable=too-many-branches
    def plot(self):
        """Generate and save the performance profile plot.

        Creates a step plot showing cumulative distribution of performance ratios
        for each solver.

        Features:
        - Step plot with solver-specific line styles and colors
        - Logarithmic x-axis scaling (if semilog=True)
        - Customizable background colors and transparency
        - Legend and grid lines
        - Automatic axis scaling

        Saves plot to filename specified during initialization.

        Raises:
            matplotlib-related exceptions: If plot generation or saving fails.

        Example:
            ```python
            # After creating profiler instance (see __init__ example)
            profiler.plot()  # Creates the performance profile plot file
            ```
        """
        self.pre_plot()

        # Hack need to background color
        figure_ = plt.figure()
        plot_ = figure_.add_subplot(111)

        # Set configurations handle when saving the plot
        save_configs = {
            "format": self.output_format,
        }

        if self.page_background:
            if not self.background:
                self.background = self.page_background
            # RGB tuples must be in the range [0,1]
            save_configs["facecolor"] = (
                self.page_background[0] / 255,
                self.page_background[1] / 255,
                self.page_background[2] / 255,
            )
        if self.background:
            plot_.set_facecolor(
                (
                    self.background[0] / 255,
                    self.background[1] / 255,
                    self.background[2] / 255,
                )
            )
            if not self.page_background:
                figure_.patch.set_alpha(0.0)
        if not self.background and not self.page_background:
            save_configs["transparent"] = True
            save_configs["facecolor"] = "none"

        # Define the linestyles
        if self.black_and_white:
            linestyles = ["k-", "k--", "k:", "k-."]
        else:
            linestyles = ["b", "g", "r", "c", "m", "y"]

        # Generate the plot for each solver
        for idx, solver in enumerate(self.solvers):
            plot_.step(
                self.times,
                self.ppsbt[solver],
                linestyles[idx],
                label=solver,
                where="post",
            )

        # Change the xscale to log scale
        if self.semilog:
            plt.gca().set_xscale("log")

        # Axis
        try:
            maxt = min(max(self.times), self.tau)
        except (AttributeError, TypeError):
            maxt = max(self.times)
        plt.gca().set_xlim(1, maxt)
        plt.gca().set_xlabel(self.plot_lang(self.xlabel))
        plt.gca().set_ylim(-0.002, 1.006)
        plt.gca().set_ylabel(self.plot_lang(self.ylabel))
        if self.title is not None:
            plt.gca().set_title(self.plot_lang(self.title))

        # Legend
        plt.gca().legend(loc=4)

        # Help lines
        plt.gca().grid(axis="y", color="0.5", linestyle="-")

        # Save the plot
        plt.savefig(self.output, bbox_inches="tight", pad_inches=0.05, **save_configs)
Functions
__init__(parser_options, profiler_options)

Parameters:

  • parser_options (dict) –

    Data parsing configuration including: - files: List of solver data files to process - success: Success criteria for solver convergence - maxtime/mintime: Time filtering bounds - compare: Performance metric to compare (default: “time”) - subset: Optional problem subset restriction

  • profiler_options (dict) –

    Visualization configuration including: - output: Output filename prefix (extension auto-added) - output_format: File format (png, pdf, svg, eps, ps) - semilog: Use logarithmic x-axis scaling - black_and_white: Use monochrome line styles - lang: Language for plot labels - title/xlabel/ylabel: Plot text customization - background/page_background: Color customization

Example
parser_opts = {"files": ["data1.txt"], "success": ["converged"]}
profiler_opts = {"output": "plot", "output_format": "png", "lang": "en"}
profiler = Profiler(parser_opts, profiler_opts)
Source code in perprof/matplotlib.py
 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
def __init__(self, parser_options, profiler_options):
    """Initialize matplotlib-based performance profiler.

    Args:
        parser_options (dict): Data parsing configuration including:
            - files: List of solver data files to process
            - success: Success criteria for solver convergence
            - maxtime/mintime: Time filtering bounds
            - compare: Performance metric to compare (default: "time")
            - subset: Optional problem subset restriction
        profiler_options (dict): Visualization configuration including:
            - output: Output filename prefix (extension auto-added)
            - output_format: File format (png, pdf, svg, eps, ps)
            - semilog: Use logarithmic x-axis scaling
            - black_and_white: Use monochrome line styles
            - lang: Language for plot labels
            - title/xlabel/ylabel: Plot text customization
            - background/page_background: Color customization

    Example:
        ```python
        parser_opts = {"files": ["data1.txt"], "success": ["converged"]}
        profiler_opts = {"output": "plot", "output_format": "png", "lang": "en"}
        profiler = Profiler(parser_opts, profiler_opts)
        ```
    """
    if profiler_options["output"] is None:
        self.output = f"performance-profile.{profiler_options['output_format']}"
    else:
        self.output = (
            f"{profiler_options['output']}.{profiler_options['output_format']}"
        )
    self.output_format = profiler_options["output_format"]

    # Language for the plot
    translation = gettext.translation(
        "perprof", os.path.join(THIS_DIR, "locale"), [profiler_options["lang"]]
    )
    self.plot_lang = translation.gettext

    prof.Pdata.__init__(self, parser_options, profiler_options)
plot()

Generate and save the performance profile plot.

Creates a step plot showing cumulative distribution of performance ratios for each solver.

Features: - Step plot with solver-specific line styles and colors - Logarithmic x-axis scaling (if semilog=True) - Customizable background colors and transparency - Legend and grid lines - Automatic axis scaling

Saves plot to filename specified during initialization.

Raises:

  • matplotlib-related exceptions –

    If plot generation or saving fails.

Example
# After creating profiler instance (see __init__ example)
profiler.plot()  # Creates the performance profile plot file
Source code in perprof/matplotlib.py
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
def plot(self):
    """Generate and save the performance profile plot.

    Creates a step plot showing cumulative distribution of performance ratios
    for each solver.

    Features:
    - Step plot with solver-specific line styles and colors
    - Logarithmic x-axis scaling (if semilog=True)
    - Customizable background colors and transparency
    - Legend and grid lines
    - Automatic axis scaling

    Saves plot to filename specified during initialization.

    Raises:
        matplotlib-related exceptions: If plot generation or saving fails.

    Example:
        ```python
        # After creating profiler instance (see __init__ example)
        profiler.plot()  # Creates the performance profile plot file
        ```
    """
    self.pre_plot()

    # Hack need to background color
    figure_ = plt.figure()
    plot_ = figure_.add_subplot(111)

    # Set configurations handle when saving the plot
    save_configs = {
        "format": self.output_format,
    }

    if self.page_background:
        if not self.background:
            self.background = self.page_background
        # RGB tuples must be in the range [0,1]
        save_configs["facecolor"] = (
            self.page_background[0] / 255,
            self.page_background[1] / 255,
            self.page_background[2] / 255,
        )
    if self.background:
        plot_.set_facecolor(
            (
                self.background[0] / 255,
                self.background[1] / 255,
                self.background[2] / 255,
            )
        )
        if not self.page_background:
            figure_.patch.set_alpha(0.0)
    if not self.background and not self.page_background:
        save_configs["transparent"] = True
        save_configs["facecolor"] = "none"

    # Define the linestyles
    if self.black_and_white:
        linestyles = ["k-", "k--", "k:", "k-."]
    else:
        linestyles = ["b", "g", "r", "c", "m", "y"]

    # Generate the plot for each solver
    for idx, solver in enumerate(self.solvers):
        plot_.step(
            self.times,
            self.ppsbt[solver],
            linestyles[idx],
            label=solver,
            where="post",
        )

    # Change the xscale to log scale
    if self.semilog:
        plt.gca().set_xscale("log")

    # Axis
    try:
        maxt = min(max(self.times), self.tau)
    except (AttributeError, TypeError):
        maxt = max(self.times)
    plt.gca().set_xlim(1, maxt)
    plt.gca().set_xlabel(self.plot_lang(self.xlabel))
    plt.gca().set_ylim(-0.002, 1.006)
    plt.gca().set_ylabel(self.plot_lang(self.ylabel))
    if self.title is not None:
        plt.gca().set_title(self.plot_lang(self.title))

    # Legend
    plt.gca().legend(loc=4)

    # Help lines
    plt.gca().grid(axis="y", color="0.5", linestyle="-")

    # Save the plot
    plt.savefig(self.output, bbox_inches="tight", pad_inches=0.05, **save_configs)