source: TOOLS/ConsoGENCMIP6/bin/plot_bilan_jobs.py @ 2849

Last change on this file since 2849 was 2849, checked in by labetoulle, 8 years ago

[gencmip6] running/pending jobs:

  • Gather data for whole Curie in addition to that from gencmip6 project
  • Plot hourly data and daily mean
  • Property svn:executable set to *
File size: 16.3 KB
RevLine 
[2437]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# this must come first
5from __future__ import print_function, unicode_literals, division
6
7# standard library imports
8from argparse import ArgumentParser
9import os
10import os.path
11import datetime as dt
[2526]12from dateutil.relativedelta import relativedelta
[2437]13import numpy as np
14
15# Application library imports
[2460]16from libconso import *
[2437]17
18
19########################################
20class DataDict(dict):
21  #---------------------------------------
22  def __init__(self):
23    self = {}
24
25  #---------------------------------------
26  def init_range(self, date_beg, date_end, inc=1):
27    """
28    """
29    delta = date_end - date_beg
30
31    (deb, fin) = (0, delta.days+1)
32
33    dates = (date_beg + dt.timedelta(days=i)
34             for i in xrange(deb, fin, inc))
35
36    for date in dates:
37      self.add_item(date)
38
39  #---------------------------------------
40  def fill_data(self, filein):
41    """
42    """
43    try:
44      data = np.genfromtxt(
45        filein,
46        skip_header=1,
47        converters={
[2849]48          0:  string_to_date,
49          1:  string_to_float,
50          2:  string_to_percent,
51          3:  string_to_percent,
52          4:  string_to_float,
53          5:  string_to_float,
54          6:  string_to_float,
55          7:  string_to_float,
56          8:  string_to_float,
57          9:  string_to_float,
58          10: string_to_float,
59          11: string_to_float,
[2437]60        },
61        missing_values="nan",
62      )
63    except Exception as rc:
64      print("Empty file {}:\n{}".format(filein, rc))
65      exit(1)
66
67    for date, conso, real_use, theo_use, \
[2849]68        run_mean, pen_mean, run_std, pen_std, \
69        _, _, _, _ in data:
[2437]70      if date in self:
71        self.add_item(
72          date,
73          conso,
74          real_use,
75          theo_use,
76          run_mean,
77          pen_mean,
78          run_std,
79          pen_std,
80        )
81        self[date].fill()
82
83  #---------------------------------------
84  def add_item(self, date, conso=np.nan,
85               real_use=np.nan, theo_use=np.nan,
86               run_mean=np.nan, pen_mean=np.nan,
87               run_std=np.nan, pen_std=np.nan):
88    """
89    """
90    self[date] = Conso(date, conso, real_use, theo_use,
91                       run_mean, pen_mean, run_std, pen_std)
92
93  #---------------------------------------
94  def theo_equation(self):
95    """
96    """
97    (dates, theo_uses) = \
98      zip(*((item.date, item.theo_use)
99            for item in self.get_items_in_full_range()))
100
101    (idx_min, idx_max) = \
102        (np.nanargmin(theo_uses), np.nanargmax(theo_uses))
103
104    x1 = dates[idx_min].timetuple().tm_yday
105    x2 = dates[idx_max].timetuple().tm_yday
106
107    y1 = theo_uses[idx_min]
108    y2 = theo_uses[idx_max]
109
[2526]110    m = np.array([[x1, 1.], [x2, 1.]], dtype="float")
111    n = np.array([y1, y2], dtype="float")
[2437]112
[2526]113    poly_ok = True
[2437]114    try:
[2526]115      poly_theo = np.poly1d(np.linalg.solve(m, n))
[2437]116    except np.linalg.linalg.LinAlgError:
[2526]117      poly_ok = False
[2437]118
[2526]119    if poly_ok:
120      delta = (dates[0] + relativedelta(months=2) - dates[0]).days
[2437]121
[2526]122      poly_delay = np.poly1d(
123        [poly_theo[1], poly_theo[0] - poly_theo[1] * delta]
124      )
125
126      self.poly_theo = poly_theo
127      self.poly_delay = poly_delay
128
[2437]129  #---------------------------------------
130  def get_items_in_range(self, date_beg, date_end, inc=1):
131    """
132    """
133    items = (item for item in self.itervalues()
134                   if item.date >= date_beg and
135                      item.date <= date_end)
136    items = sorted(items, key=lambda item: item.date)
137
138    return items[::inc]
139
140  #---------------------------------------
141  def get_items_in_full_range(self, inc=1):
142    """
143    """
144    items = (item for item in self.itervalues())
145    items = sorted(items, key=lambda item: item.date)
146
147    return items[::inc]
148
149  #---------------------------------------
150  def get_items(self, inc=1):
151    """
152    """
153    items = (item for item in self.itervalues()
154                   if item.isfilled())
155    items = sorted(items, key=lambda item: item.date)
156
157    return items[::inc]
158
159
160class Conso(object):
161  #---------------------------------------
162  def __init__(self, date, conso=np.nan,
163               real_use=np.nan, theo_use=np.nan,
164               run_mean=np.nan, pen_mean=np.nan,
165               run_std=np.nan, pen_std=np.nan):
166    self.date     = date
167    self.conso    = conso
168    self.real_use = real_use
169    self.theo_use = theo_use
[2526]170    self.poly_theo = np.poly1d([])
171    self.poly_delay = np.poly1d([])
[2437]172    self.run_mean = run_mean
173    self.pen_mean = pen_mean
174    self.run_std  = run_std
175    self.pen_std  = pen_std
176    self.filled   = False
177
178  #---------------------------------------
179  def __repr__(self):
180    return "{:.2f} ({:.2%})".format(self.conso, self.real_use)
181
182  #---------------------------------------
183  def isfilled(self):
184    return self.filled
185
186  #---------------------------------------
187  def fill(self):
188    self.filled = True
189
190
191########################################
192def plot_init():
193  paper_size  = np.array([29.7, 21.0])
194  fig, (ax_conso, ax_jobs) = plt.subplots(
195    nrows=2,
196    ncols=1,
197    sharex=True,
198    squeeze=True,
199    figsize=(paper_size/2.54)
200  )
201  ax_theo = ax_conso.twinx()
202
203  return fig, ax_conso, ax_theo, ax_jobs
204
205
206########################################
207def plot_data(ax_conso, ax_theo, ax_jobs, xcoord, dates,
[2526]208              consos, theo_uses, real_uses, theo_equs, theo_delay,
[2437]209              run_mean, pen_mean, run_std, pen_std):
210  """
211  """
212  line_style = "-"
213  if args.full:
214    line_width = 0.05
215  else:
216    line_width = 0.1
217
[2460]218  ax_conso.bar(
219    xcoord, consos, width=1, align="center", color="linen",
220    linewidth=line_width, label="conso (heures)"
221  )
[2437]222
[2460]223  ax_theo.plot(
[2526]224    xcoord, real_uses, line_style,
225    color="forestgreen", linewidth=1, markersize=8,
226    solid_capstyle="round", solid_joinstyle="round",
227    label="conso\nréelle (%)"
228  )
229  ax_theo.plot(
[2460]230    xcoord, theo_equs, "--",
231    color="firebrick", linewidth=0.5,
232    solid_capstyle="round", solid_joinstyle="round"
233  )
234  ax_theo.plot(
[2526]235    xcoord, theo_uses, line_style,
236    color="firebrick", linewidth=1, markersize=8,
[2460]237    solid_capstyle="round", solid_joinstyle="round",
238    label="conso\nthéorique (%)"
239  )
240  ax_theo.plot(
[2526]241    xcoord, theo_delay, ":",
242    color="firebrick", linewidth=0.5,
[2460]243    solid_capstyle="round", solid_joinstyle="round",
[2526]244    label="retard de\ndeux mois (%)"
[2460]245  )
[2437]246
247  line_width = 0.
248  width = 1.05
249
[2460]250  ax_jobs.bar(
251    xcoord, run_mean, width=width, align="center",
252    # yerr=run_std/2, ecolor="green",
253    color="lightgreen", linewidth=line_width,
254    antialiased=True, label="jobs running"
255  )
256  ax_jobs.bar(
257    xcoord, pen_mean, bottom=run_mean, width=width, align="center",
258    # yerr=pen_std/2, ecolor="darkred",
259    color="firebrick", linewidth=line_width,
260    antialiased=True, label="jobs pending"
261  )
[2437]262
263
264########################################
[2460]265def plot_config(fig, ax_conso, ax_theo, xcoord, dates, title,
[2631]266                conso_per_day, conso_per_day_2):
[2437]267  """
268  """
[2632]269  from matplotlib.ticker import AutoMinorLocator
270
[2437]271  # ... Config axes ...
272  # -------------------
273  # 1) Range
[2448]274  conso_max = np.nanmax(consos)
[2717]275  jobs_max = np.nanmax(run_mean)
[2448]276  if args.max:
[2717]277    ymax = conso_max  # * 1.1
278    ymax_jobs = jobs_max  # * 1.1
[2448]279  else:
[2631]280    ymax = 3. * max(conso_per_day, conso_per_day_2)
[2717]281    ymax_jobs = max(conso_per_day, conso_per_day_2)/24.
[2437]282
[2448]283  if conso_max > ymax:
284    ax_conso.annotate(
285      "{:.2e} heures".format(conso_max),
286      ha="left",
287      va="top",
288      fontsize="xx-small",
289      bbox=dict(boxstyle="round", fc="w", ec="0.5", color="gray",),
[2449]290      # xy=(np.nanargmax(consos)+1.2, ymax),
[2448]291      xy=(np.nanargmax(consos)+1.2, ymax*0.9),
292      textcoords="axes fraction",
293      xytext=(0.01, 0.8),
294      arrowprops=dict(
295        arrowstyle="->",
296        shrinkA=0,
297        shrinkB=0,
298        color="gray",
299      ),
300    )
301
[2437]302  xmin, xmax = xcoord[0]-1, xcoord[-1]+1
303  ax_conso.set_xlim(xmin, xmax)
[2448]304  ax_conso.set_ylim(0., ymax)
[2717]305  ax_jobs.set_ylim(0., ymax_jobs)
[2437]306  ax_theo.set_ylim(0., 100)
307
[2717]308  # 2) Plot ideal daily consumption in hours
309  line_color = "blue"
310  line_alpha = 0.5
311  line_label = "conso journaliÚre\nidéale ({})"
312  for ax, y_div, label in (
313    (ax_conso, 1., line_label.format("heures")),
314    (ax_jobs, 24., line_label.format("cœurs")),
315  ):
316    if conso_per_day_2:
317      list_x = [0, xmax/2, xmax/2, xmax]
318      list_y = np.array(
319        [conso_per_day, conso_per_day, conso_per_day_2, conso_per_day_2],
320        dtype=float
321      )
322      ax.plot(
323        list_x, list_y/y_div,
324        color=line_color, alpha=line_alpha, label=label,
325      )
326    else:
327      ax.axhline(
328        y=conso_per_day/y_div,
329        color=line_color, alpha=line_alpha, label=label,
330      )
331
332  # 3) Ticks labels
[2437]333  (date_beg, date_end) = (dates[0], dates[-1])
334  date_fmt = "{:%d-%m}"
335
336  if date_end - date_beg > dt.timedelta(weeks=9):
337    maj_xticks = [x for x, d in zip(xcoord, dates)
338                     if d.weekday() == 0]
339    maj_xlabs  = [date_fmt.format(d) for d in dates
340                     if d.weekday() == 0]
341  else:
342    maj_xticks = [x for x, d in zip(xcoord, dates)]
343    maj_xlabs  = [date_fmt.format(d) for d in dates]
344
345  ax_conso.ticklabel_format(axis="y", style="sci", scilimits=(0, 0))
346
347  ax_jobs.set_xticks(xcoord, minor=True)
348  ax_jobs.set_xticks(maj_xticks, minor=False)
[2460]349  ax_jobs.set_xticklabels(
350    maj_xlabs, rotation="vertical", size="x-small"
351  )
[2437]352
353  for ax, y, label in (
354    (ax_conso, conso_per_day, "heures"),
[2631]355    (ax_jobs, conso_per_day / 24., "cœurs"),
[2437]356  ):
[2632]357    minor_locator = AutoMinorLocator()
358    ax.yaxis.set_minor_locator(minor_locator)
359
[2437]360    yticks = list(ax.get_yticks())
361    yticks.append(y)
362    ax.set_yticks(yticks)
363
[2631]364  if conso_per_day_2:
365    yticks.append(conso_per_day_2)
366
[2448]367  ax_theo.spines["right"].set_color("firebrick")
368  ax_theo.tick_params(colors="firebrick")
369  ax_theo.yaxis.label.set_color("firebrick")
370
[2437]371  for x, d in zip(xcoord, dates):
372    if d.weekday() == 0 and d.hour == 0:
373      for ax in (ax_conso, ax_jobs):
374        ax.axvline(x=x, color="black", alpha=0.5,
375                   linewidth=0.5, linestyle=":")
376
[2717]377  # 4) Define axes title
[2437]378  for ax, label in (
379    (ax_conso, "heures"),
380    (ax_theo, "%"),
381    (ax_jobs, "cœurs"),
382  ):
383    ax.set_ylabel(label, fontweight="bold")
384    ax.tick_params(axis="y", labelsize="small")
385
[2717]386  # 5) Define plot size
[2437]387  fig.subplots_adjust(
388    left=0.08,
389    bottom=0.09,
390    right=0.93,
391    top=0.93,
392    hspace=0.1,
393    wspace=0.1,
394  )
395
396  # ... Main title and legend ...
397  # -----------------------------
398  fig.suptitle(title, fontweight="bold", size="large")
399  for ax, loc in (
400    (ax_conso, "upper left"),
401    (ax_theo, "upper right"),
402    (ax_jobs, "upper left"),
403  ):
404    ax.legend(loc=loc, fontsize="x-small", frameon=False)
405
406
407########################################
408def get_arguments():
409  parser = ArgumentParser()
410  parser.add_argument("-v", "--verbose", action="store_true",
411                      help="verbose mode")
412  parser.add_argument("-f", "--full", action="store_true",
413                      help="plot the whole period")
414  parser.add_argument("-i", "--increment", action="store",
415                      type=int, default=1, dest="inc",
416                      help="sampling increment")
417  parser.add_argument("-r", "--range", action="store", nargs=2,
418                      type=string_to_date,
419                      help="date range: ssaa-mm-jj ssaa-mm-jj")
420  parser.add_argument("-m", "--max", action="store_true",
421                      help="plot with y_max = allocation")
422  parser.add_argument("-s", "--show", action="store_true",
423                      help="interactive mode")
424  parser.add_argument("-d", "--dods", action="store_true",
425                      help="copy output on dods")
426
427  return parser.parse_args()
428
429
430########################################
431if __name__ == '__main__':
432
433  # .. Initialization ..
434  # ====================
435  # ... Command line arguments ...
436  # ------------------------------
437  args = get_arguments()
438
439  # ... Turn interactive mode off ...
440  # ---------------------------------
441  if not args.show:
442    import matplotlib
443    matplotlib.use('Agg')
444
445  import matplotlib.pyplot as plt
446  # from matplotlib.backends.backend_pdf import PdfPages
447
448  if not args.show:
449    plt.ioff()
450
451  # ... Files and directories ...
452  # -----------------------------
[2460]453  project_name, DIR, OUT = parse_config("bin/config.ini")
454
[2437]455  (file_param, file_utheo, file_data) = \
456      get_input_files(DIR["SAVEDATA"],
457                      [OUT["PARAM"], OUT["UTHEO"], OUT["BILAN"]])
458
[2526]459  img_name = os.path.splitext(
460               os.path.basename(__file__)
461             )[0].replace("plot_", "")
462
[2437]463  today = os.path.basename(file_param).strip(OUT["PARAM"])
464
465  if args.verbose:
[2526]466    fmt_str = "{:10s} : {}"
467    print(fmt_str.format("args", args))
468    print(fmt_str.format("today", today))
469    print(fmt_str.format("file_param", file_param))
470    print(fmt_str.format("file_utheo", file_utheo))
471    print(fmt_str.format("file_data", file_data))
472    print(fmt_str.format("img_name", img_name))
[2437]473
474  # .. Get project info ..
475  # ======================
[2464]476  projet = Project(project_name)
[2460]477  projet.fill_data(file_param)
478  projet.get_date_init(file_utheo)
[2437]479
480  # .. Fill in data ..
481  # ==================
482  # ... Initialization ...
483  # ----------------------
484  bilan = DataDict()
[2460]485  bilan.init_range(projet.date_init, projet.deadline)
[2437]486  # ... Extract data from file ...
487  # ------------------------------
488  bilan.fill_data(file_data)
489  # ... Compute theoratical use from known data  ...
490  # ------------------------------------------------
491  bilan.theo_equation()
492
493  # .. Extract data depending on C.L. arguments ..
494  # ==============================================
495  if args.full:
496    selected_items = bilan.get_items_in_full_range(args.inc)
497  elif args.range:
498    selected_items = bilan.get_items_in_range(
499      args.range[0], args.range[1], args.inc
500    )
501  else:
502    selected_items = bilan.get_items(args.inc)
503
504  # .. Compute data to be plotted ..
505  # ================================
506  nb_items = len(selected_items)
507
[2526]508  xcoord = np.linspace(1, nb_items, num=nb_items)
509  dates = [item.date for item in selected_items]
[2437]510
[2526]511  cumul = np.array([item.conso for item in selected_items],
[2437]512                        dtype=float)
[2526]513  consos = []
[2437]514  consos.append(cumul[0])
515  consos[1:nb_items] = cumul[1:nb_items] - cumul[0:nb_items-1]
[2526]516  consos = np.array(consos, dtype=float)
[2437]517
[2631]518  if projet.project == "gencmip6":
[2717]519    alloc1 = (1 * projet.alloc) / 3
520    alloc2 = (2 * projet.alloc) / 3
[2631]521    conso_per_day   = 2 * alloc1 / projet.days
522    conso_per_day_2 = 2 * alloc2 / projet.days
523  else:
524    conso_per_day = projet.alloc / projet.days
525    conso_per_day_2 = None
[2437]526
[2526]527  theo_uses = np.array(
528    [100.*item.theo_use for item in selected_items],
529    dtype=float
530  )
531  real_uses = np.array(
532    [100.*item.real_use for item in selected_items],
533    dtype=float
534  )
535  theo_equs = np.array(
536    [100. * bilan.poly_theo(date.timetuple().tm_yday)
537      for date in dates],
538    dtype=float
539  )
540  theo_delay = np.array(
541    [100. * bilan.poly_delay(date.timetuple().tm_yday)
542      for date in dates],
543    dtype=float
544  )
[2437]545
546  run_mean = np.array([item.run_mean for item in selected_items],
547                       dtype=float)
548  pen_mean = np.array([item.pen_mean for item in selected_items],
549                       dtype=float)
550  run_std  = np.array([item.run_std for item in selected_items],
551                       dtype=float)
552  pen_std  = np.array([item.pen_std for item in selected_items],
553                       dtype=float)
554
555  # .. Plot stuff ..
556  # ================
557  # ... Initialize figure ...
558  # -------------------------
559  (fig, ax_conso, ax_theo, ax_jobs) = plot_init()
560
561  # ... Plot data ...
562  # -----------------
563  plot_data(ax_conso, ax_theo, ax_jobs, xcoord, dates,
[2526]564            consos, theo_uses, real_uses, theo_equs, theo_delay,
[2437]565            run_mean, pen_mean, run_std, pen_std)
566
567  # ... Tweak figure ...
568  # --------------------
[2448]569  title = "{} : Conso globale et suivi des jobs " \
570          "(moyenne journaliÚre)\n({:%d/%m/%Y} - {:%d/%m/%Y})".format(
[2460]571            projet.project.upper(),
572            projet.date_init,
573            projet.deadline
[2448]574          )
[2437]575
[2460]576  plot_config(
[2631]577    fig, ax_conso, ax_theo, xcoord, dates, title, 
578    conso_per_day, conso_per_day_2
[2460]579  )
[2437]580
581  # ... Save figure ...
582  # -------------------
583  img_in  = os.path.join(DIR["PLOT"], "{}.pdf".format(img_name))
584  img_out = os.path.join(DIR["SAVEPLOT"],
585                         "{}_{}.pdf".format(img_name, today))
586
[2460]587  plot_save(img_in, img_out, title, DIR)
[2437]588
589  # ... Publish figure on dods ...
590  # ------------------------------
591  if args.dods:
[2526]592    if args.verbose:
593      print("Publish figure on dods")
[2460]594    dods_cp(img_in, DIR)
[2437]595
596  if args.show:
597    plt.show()
598
599  exit(0)
600
Note: See TracBrowser for help on using the repository browser.