xref: /libCEED/benchmarks/postprocess_plot.py (revision 5aed82e4fa97acf4ba24a7f10a35f5303a6798e0)
1#!/usr/bin/env python3
2
3# Copyright (c) 2017-2024, Lawrence Livermore National Security, LLC and other CEED contributors.
4# All Rights Reserved. See the top-level LICENSE and NOTICE files for details.
5#
6# SPDX-License-Identifier: BSD-2-Clause
7#
8# This file is part of CEED:  http://github.com/ceed
9
10from pylab import *
11from matplotlib import use
12from postprocess_base import read_logs
13import pandas as pd
14
15# Adjustable plot parameters
16log_y = 0               # use log scale on the y-axis?
17x_range = (1e1, 4e6)     # plot range for the x-axis; comment out for auto
18y_range = (0, 2e9)       # plot range for the y-axis; comment out for auto
19draw_iter_lines = 0     # draw the "iter/s" lines?
20ymin_iter_lines = 3e5   # minimal y value for the "iter/s" lines
21ymax_iter_lines = 8e8   # maximal y value for the "iter/s" lines
22legend_ncol = (2 if log_y else 1)   # number of columns in the legend
23write_figures = 1       # save the figures to files?
24show_figures = 1        # display the figures on the screen?
25
26# Load the data
27runs = read_logs()
28
29# Sample plot output
30if not show_figures:
31    use('pdf')
32
33rcParams['font.sans-serif'].insert(0, 'Noto Sans')
34rcParams['font.sans-serif'].insert(1, 'Open Sans')
35rcParams['figure.figsize'] = [10, 8]  # default: 8 x 6
36
37cm_size = 16
38colors = ['dimgrey', 'black', 'saddlebrown', 'firebrick', 'red', 'orange',
39          'gold', 'lightgreen', 'green', 'cyan', 'teal', 'blue', 'navy',
40          'purple', 'magenta', 'pink']
41
42# Get test names
43sel_runs = runs
44tests = list(sel_runs.test.unique())
45test = tests[0]
46
47# Run information
48print('Using test:', test)
49
50if 'CEED Benchmark Problem' in test:
51    test_short = test.strip().split()[0] + ' BP' + test.strip().split()[-1]
52
53# Plot same BP
54sel_runs = sel_runs.loc[sel_runs['test'] == test]
55
56# Plot same case (scalar vs vector)
57cases = list(sel_runs.case.unique())
58case = cases[0]
59vdim = 1 if case == 'scalar' else 3
60print('Using case:', case)
61sel_runs = sel_runs.loc[sel_runs['case'] == case]
62
63# Plot same 'code'
64codes = list(sel_runs.code.unique())
65code = codes[0]
66sel_runs = sel_runs.loc[sel_runs['code'] == code]
67
68# Group plots by backend and number of processes
69pl_set = sel_runs[['backend',
70                   'backend_memtype',
71                   'num_procs',
72                   'num_procs_node']]
73pl_set = pl_set.drop_duplicates()
74
75# Plotting
76for index, row in pl_set.iterrows():
77    backend = row['backend']
78    backend_memtype = row['backend_memtype']
79    num_procs = float(row['num_procs'])
80    num_procs_node = float(row['num_procs_node'])
81    num_nodes = num_procs / num_procs_node
82    pl_runs = sel_runs[(sel_runs.backend == backend) |
83                       (sel_runs.num_procs == num_procs) |
84                       (sel_runs.num_procs_node == num_procs_node)]
85    if len(pl_runs.index) == 0:
86        continue
87
88    print('backend: %s, compute nodes: %i, number of MPI tasks = %i' % (
89        backend, num_nodes, num_procs))
90
91    figure()
92    i = 0
93    sol_p_set = sel_runs['degree'].drop_duplicates()
94    sol_p_set = sol_p_set.sort_values()
95    # Iterate over P
96    for sol_p in sol_p_set:
97        qpts = sel_runs['quadrature_pts'].loc[pl_runs['degree'] == sol_p]
98        qpts = qpts.drop_duplicates().sort_values(ascending=False)
99        qpts = qpts.reset_index(drop=True)
100        print('Degree: %i, quadrature points:' % sol_p, qpts[0])
101        # Generate plot data
102        d = [[run['degree'], run['num_elem'], 1. * run['num_unknowns'] / num_nodes / vdim,
103              run['cg_iteration_dps'] / num_nodes]
104             for index, run in
105             pl_runs.loc[(pl_runs['degree'] == sol_p) |
106                         (pl_runs['quadrature_pts'] == qpts[0])].iterrows()]
107        d = [[e[2], e[3]] for e in d if e[0] == sol_p]
108        # (DOFs/[sec/iter]/node)/(DOFs/node) = iter/sec
109        d = [[nun,
110              min([e[1] for e in d if e[0] == nun]),
111              max([e[1] for e in d if e[0] == nun])]
112             for nun in set([e[0] for e in d])]
113        d = asarray(sorted(d))
114        # Plot
115        plot(d[:, 0], d[:, 2], 'o-', color=colors[i % cm_size],
116             label='p=%i' % sol_p)
117        if list(d[:, 1]) != list(d[:, 2]):
118            plot(d[:, 0], d[:, 1], 'o-', color=colors[i])
119            fill_between(d[:, 0], d[:, 1], d[:, 2],
120                         facecolor=colors[i], alpha=0.2)
121        # Continue if only 1 set of qpts
122        if len(qpts) == 1:
123            i = i + 1
124            continue
125        # Second set of qpts
126        d = [[run['degree'], run['num_elem'], 1. * run['num_unknowns'] / num_nodes / vdim,
127              run['cg_iteration_dps'] / num_nodes]
128             for index, run in
129             pl_runs.loc[(pl_runs['degree'] == sol_p) |
130                         (pl_runs['quadrature_pts'] == qpts[1])].iterrows()]
131        d = [[e[2], e[3]] for e in d if e[0] == sol_p]
132        if len(d) == 0:
133            i = i + 1
134            continue
135        d = [[nun,
136              min([e[1] for e in d if e[0] == nun]),
137              max([e[1] for e in d if e[0] == nun])]
138             for nun in set([e[0] for e in d])]
139        d = asarray(sorted(d))
140        plot(d[:, 0], d[:, 2], 's--', color=colors[i],
141             label='p=%i' % sol_p)
142        if list(d[:, 1]) != list(d[:, 2]):
143            plot(d[:, 0], d[:, 1], 's--', color=colors[i])
144        ##
145        i = i + 1
146    ##
147    if draw_iter_lines:
148        y0, y1 = ymin_iter_lines, ymax_iter_lines
149        y = asarray([y0, y1]) if log_y else exp(linspace(log(y0), log(y1)))
150        slope1 = 600.
151        slope2 = 6000.
152        plot(y / slope1, y, 'k--', label='%g iter/s' % (slope1 / vdim))
153        plot(y / slope2, y, 'k-', label='%g iter/s' % (slope2 / vdim))
154
155    # Plot information
156    title(r'%i node%s $\times$ %i ranks, %s, %s, %s' % (
157          num_nodes, '' if num_nodes == 1 else 's',
158          num_procs_node, backend, backend_memtype, test_short), fontsize=16)
159    xscale('log')  # subsx=[2,4,6,8]
160    if log_y:
161        yscale('log')
162    if 'x_range' in vars() and len(x_range) == 2:
163        xlim(x_range)
164    if 'y_range' in vars() and len(y_range) == 2:
165        ylim(y_range)
166    grid('on', color='gray', ls='dotted')
167    grid('on', axis='both', which='minor', color='gray', ls='dotted')
168    plt.tick_params(labelsize=14)
169    exptext = gca().yaxis.get_offset_text()
170    exptext.set_size(14)
171    gca().set_axisbelow(True)
172    xlabel('Points per compute node', fontsize=14)
173    ylabel('[DOFs x CG iterations] / [compute nodes x seconds]', fontsize=14)
174    legend(ncol=legend_ncol, loc='best', fontsize=13)
175
176    # Write
177    if write_figures:  # write .pdf file?
178        short_backend = backend.replace('/', '')
179        test_short_save = test_short.replace(' ', '')
180        pdf_file = 'plot_%s_%s_%s_%s_N%03i_pn%i.pdf' % (
181            code, test_short_save, short_backend, backend_memtype, num_nodes, num_procs_node)
182        print('\nsaving figure --> %s' % pdf_file)
183        savefig(pdf_file, format='pdf', bbox_inches='tight')
184
185if show_figures:  # show the figures?
186    print('\nShowing figures ...')
187    show()
188