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

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