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

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

allow two sub-allocation periods, part 1

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