source: TOOLS/ConsoGENCI/trunk/bin/plot_bilan.py @ 2783

Last change on this file since 2783 was 2783, checked in by labetoulle, 6 years ago
  • Run everything (DB inserts + plots) from single bash script
  • Change exit codes (unsigned in bash, so no negative values...) :
    • 0: everything was ok;
    • 1: nothing done, 'cause nothing to do => ok;
    • >= 2: error.
  • DB access now needs password
  • plot_bilan: added date of production and plotted range to image
  • Cleaning (useless comments, ...)
File size: 17.9 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# ==================================================================== #
5# Author: Sonia Labetoulle                                             #
6# Contact: sonia.labetoulle _at_ ipsl.jussieu.fr                       #
7# Created: 2016                                                        #
8# History:                                                             #
9# Modification:                                                        #
10# ==================================================================== #
11
12# this must come first
13from __future__ import print_function, unicode_literals, division
14
15# standard library imports
16from argparse import ArgumentParser
17import datetime as dt
18import numpy as np
19
20# Application library imports
21from libconso import *
22import libconso_db as cdb
23import db_data
24
25
26########################################################################
27class PlotData(object):
28  #---------------------------------------------------------------------
29  def __init__(self, date_min, date_max):
30    self.date_min = date_min
31    self.date_max = date_max
32
33    self.data = DataDict()
34    self.data.init_range(self.date_min, self.date_max)
35
36
37########################################################################
38class DataDict(dict):
39  #---------------------------------------------------------------------
40  def __init__(self):
41    self = {}
42
43  #---------------------------------------------------------------------
44  def init_range(self, date_beg, date_end, inc=1):
45    """
46    """
47    delta = date_end - date_beg
48
49    (deb, fin) = (0, delta.days+1)
50
51    dates = (date_beg + dt.timedelta(days=i)
52             for i in xrange(deb, fin, inc))
53
54    for date in dates:
55      self.add_item(date)
56
57  #---------------------------------------------------------------------
58  def fill_data(self, projet):
59    """
60    """
61
62    id_condition = (
63      "({})".format(
64        " OR ".join(
65          [
66            "allocation_id={}".format(item.alloc_id)
67            for item in projet.alloc_items
68          ]
69        )
70      )
71    )
72
73    table_name = "conso.tbl_consumption"
74    request = (
75      "SELECT date, total_hrs, allocation_id "
76      "FROM " + table_name + " "
77      "WHERE login IS NULL "
78      "  AND " + id_condition + " "
79      "ORDER BY date"
80      ";"
81    )
82
83    if args.verbose:
84      print("Access table \"{}\"".format(table_name))
85      print(request)
86    cdb.select_db(cursor, request)
87
88    for date, conso, alloc_id in cursor:
89      if date.date() in self:
90        real_use = conso / projet.alloc
91        self.add_item(
92          date=date.date(),
93          conso=conso,
94          real_use=real_use,
95        )
96        self[date.date()].fill()
97
98  #---------------------------------------------------------------------
99  def add_item(self, date, conso=np.nan,
100               real_use=np.nan, theo_use=np.nan,
101               run_mean=np.nan, pen_mean=np.nan,
102               run_std=np.nan, pen_std=np.nan):
103    """
104    """
105    # self[date.date()] = Conso(
106    self[date] = Conso(
107      date, conso,
108      real_use, theo_use,
109      run_mean, pen_mean,
110      run_std, pen_std
111    )
112
113  #---------------------------------------------------------------------
114  def get_items_in_range(self, date_beg, date_end, inc=1):
115    """
116    """
117    items = (item for item in self.itervalues()
118                   if item.date >= date_beg and
119                      item.date <= date_end)
120    items = sorted(items, key=lambda item: item.date)
121
122    return items[::inc]
123
124  #---------------------------------------------------------------------
125  def get_items_in_full_range(self, inc=1):
126    """
127    """
128
129    items = (item for item in self.itervalues())
130    items = sorted(items, key=lambda item: item.date)
131
132    return items[::inc]
133
134  #---------------------------------------------------------------------
135  def get_items(self, inc=1):
136    """
137    """
138    items = (item for item in self.itervalues()
139                   if item.isfilled())
140    items = sorted(items, key=lambda item: item.date)
141
142    return items[::inc]
143
144
145class Conso(object):
146  #---------------------------------------------------------------------
147  def __init__(self, date, conso=np.nan,
148               real_use=np.nan, theo_use=np.nan,
149               run_mean=np.nan, pen_mean=np.nan,
150               run_std=np.nan, pen_std=np.nan):
151    self.date     = date
152    self.conso    = conso
153    self.real_use = real_use
154    self.theo_use = theo_use
155    self.poly_theo = np.poly1d([])
156    self.poly_delay = np.poly1d([])
157    self.run_mean = run_mean
158    self.pen_mean = pen_mean
159    self.run_std  = run_std
160    self.pen_std  = pen_std
161    self.filled   = False
162
163  #---------------------------------------------------------------------
164  def __repr__(self):
165    return "{:.2f} ({:.2%})".format(self.conso, self.real_use)
166
167  #---------------------------------------------------------------------
168  def isfilled(self):
169    return self.filled
170
171  #---------------------------------------------------------------------
172  def fill(self):
173    self.filled = True
174
175
176########################################################################
177def plot_init():
178  paper_size  = np.array([29.7, 21.0])
179  fig, ax_conso = plt.subplots(figsize=(paper_size/2.54))
180  ax_theo = ax_conso.twinx()
181
182  return fig, ax_conso, ax_theo
183
184
185########################################################################
186def plot_data(
187  ax_conso, ax_theo, xcoord, dates,
188  consos, real_uses, theo_uses, delay_uses,
189  run_mean, pen_mean, run_std, pen_std
190):
191  """
192  """
193  line_style = "-"
194  if args.full:
195    line_width = 0.05
196  else:
197    line_width = 0.1
198
199  ax_conso.bar(
200    xcoord, consos, width=1, align="center", color="linen",
201    linewidth=line_width, label="conso (heures)"
202  )
203
204  ax_theo.plot(
205    xcoord, real_uses, line_style,
206    color="forestgreen", linewidth=1, markersize=8,
207    solid_capstyle="round", solid_joinstyle="round",
208    label="conso\nréelle (%)"
209  )
210
211  ax_theo.plot(
212    xcoord, theo_uses, "--",
213    color="firebrick", linewidth=0.5,
214    solid_capstyle="round", solid_joinstyle="round",
215    label="conso\nthéorique (%)"
216  )
217
218  ax_theo.plot(
219    xcoord, delay_uses, ":",
220    color="firebrick", linewidth=0.5,
221    solid_capstyle="round", solid_joinstyle="round",
222    label="retard de\ndeux mois (%)"
223  )
224
225
226########################################################################
227def plot_config(
228  fig, ax_conso, ax_theo,
229  xcoord, dates, max_real_use,
230  title, faitle, projet, bilan_plot
231):
232  """
233  """
234  from matplotlib.ticker import AutoMinorLocator
235
236  # ... Config axes ...
237  # -------------------
238  # 1) Range
239  conso_max = np.nanmax(consos)
240  if args.max:
241    ymax = conso_max  # + conso_max*.1
242  else:
243    ymax = 3. * projet.max_daily_conso
244
245  if conso_max > ymax:
246    ax_conso.annotate(
247      "{:.2e} heures".format(conso_max),
248      ha="left",
249      va="top",
250      fontsize="xx-small",
251      bbox=dict(boxstyle="round", fc="w", ec="0.5", color="gray",),
252      xy=(np.nanargmax(consos)+1.2, ymax),
253      textcoords="axes fraction",
254      xytext=(0.01, 0.9),
255      arrowprops=dict(
256        arrowstyle="->",
257        shrinkA=0,
258        shrinkB=0,
259        color="gray",
260      ),
261    )
262
263  xmin, xmax = xcoord[0]-1, xcoord[-1]+1
264  ax_conso.set_xlim(xmin, xmax)
265  ax_conso.set_ylim(0., ymax)
266  ax_theo.set_ylim(0., 100)
267
268  # 2) Plot ideal daily consumption in hours
269  #    Plot last real use value
270  x_list = []
271  y_list = []
272  conso_yticks = list(ax_conso.get_yticks())
273  for item in projet.alloc_items:
274    x_list.extend([item.xi, item.xf])
275    y_list.extend([item.daily_conso, item.daily_conso])
276    conso_yticks.append(item.daily_conso)
277  line_alpha = 0.5
278  line_label = "conso journaliÚre\nidéale (heures)"
279  ax_conso.plot(
280    x_list, y_list,
281    linewidth=0.5,
282    color="blue",
283    alpha=line_alpha,
284    label=line_label,
285  )
286
287  theo_yticks = list(ax_theo.get_yticks())
288  theo_yticks.append(max_real_use)
289  ax_theo.axhline(
290    y=max_real_use,
291    linestyle=":", linewidth=0.5,
292    color="green", alpha=line_alpha,
293  )
294
295  # 3) Ticks labels
296  (date_beg, date_end) = (dates[0], dates[-1])
297  date_fmt = "{:%d-%m}"
298
299  if date_end - date_beg > dt.timedelta(weeks=9):
300    maj_xticks = [x for x, d in zip(xcoord, dates)
301                     if d.weekday() == 0]
302    maj_xlabs  = [date_fmt.format(d) for d in dates
303                     if d.weekday() == 0]
304  else:
305    maj_xticks = [x for x, d in zip(xcoord, dates)]
306    maj_xlabs  = [date_fmt.format(d) for d in dates]
307
308  ax_conso.ticklabel_format(axis="y", style="sci", scilimits=(0, 0))
309
310  ax_conso.set_xticks(xcoord, minor=True)
311  ax_conso.set_xticks(maj_xticks, minor=False)
312  ax_conso.set_xticklabels(
313    maj_xlabs, rotation="vertical", size="x-small"
314  )
315
316  minor_locator = AutoMinorLocator()
317  ax_conso.yaxis.set_minor_locator(minor_locator)
318  minor_locator = AutoMinorLocator()
319  ax_theo.yaxis.set_minor_locator(minor_locator)
320
321  ax_conso.set_yticks(conso_yticks)
322  ax_theo.set_yticks(theo_yticks)
323
324  ax_theo.spines["right"].set_color("firebrick")
325  ax_theo.tick_params(colors="firebrick")
326  ax_theo.yaxis.label.set_color("firebrick")
327
328  for x, d in zip(xcoord, dates):
329    if d.weekday() == 0:
330      ax_conso.axvline(
331        x=x, color="black", alpha=0.5,
332        linewidth=0.5, linestyle=":"
333      )
334
335  # 4) Define axes title
336  for ax, label in (
337    (ax_conso, "heures"),
338    (ax_theo, "%"),
339  ):
340    ax.set_ylabel(label, fontweight="bold")
341    ax.tick_params(axis="y", labelsize="small")
342
343  # 5) Define plot size
344  fig.subplots_adjust(
345    left=0.08,
346    bottom=0.09,
347    right=0.93,
348    top=0.92,
349  )
350
351  # ... Main title and legend ...
352  # -----------------------------
353  fig.suptitle(title, fontweight="bold", size="large")
354  for ax, loc in (
355    (ax_conso, "upper left"),
356    (ax_theo, "upper right"),
357  ):
358    ax.legend(loc=loc, fontsize="x-small", frameon=False)
359
360  alloc_label = (
361    "Allocation(s):\n" +
362    "\n".join([
363      "{:%d/%m/%Y}-{:%d/%m/%Y} : {:10,.0f}h".format(
364        item.start_date, item.end_date, item.alloc
365      ) for item in projet.alloc_items
366    ])
367  )
368  plt.figtext(
369    x=0.92, y=0.93, s=alloc_label.replace(",", " "),
370    backgroundcolor="linen",
371    ha="right", va="bottom", fontsize="x-small"
372  )
373
374  range_label = "{:%d/%m/%Y} - {:%d/%m/%Y}".format(
375    bilan_plot.date_min,
376    bilan_plot.date_max,
377  )
378  plt.figtext(
379    x=0.5, y=0.93, s=range_label,
380    ha="center", va="bottom", fontsize="large"
381  )
382
383  faitle_label = "fait le {:%d/%m/%Y}".format(bilan_plot.faitle)
384  plt.figtext(
385    x=0.02, y=0.98, s=faitle_label,
386    ha="left", va="top", fontsize="x-small"
387  )
388
389
390########################################################################
391def get_arguments():
392  parser = ArgumentParser()
393  parser.add_argument("project", action="store",
394                      help="Project name")
395  parser.add_argument("center", action="store",
396                      help="Center name (idris/tgcc)")
397
398  parser.add_argument("-v", "--verbose", action="store_true",
399                      help="verbose mode")
400  parser.add_argument("-f", "--full", action="store_true",
401                      help="plot the whole period")
402  parser.add_argument("-i", "--increment", action="store",
403                      type=int, default=1, dest="inc",
404                      help="sampling increment")
405  parser.add_argument("-r", "--range", action="store", nargs=2,
406                      type=string_to_date,
407                      help="date range: ssaa-mm-jj ssaa-mm-jj")
408  parser.add_argument("-m", "--max", action="store_true",
409                      help="plot with y_max = allocation")
410  parser.add_argument("-s", "--show", action="store_true",
411                      help="interactive mode")
412  parser.add_argument("-d", "--dods", action="store_true",
413                      help="copy output on dods")
414
415  return parser.parse_args()
416
417
418########################################################################
419if __name__ == '__main__':
420
421  # .. Initialization ..
422  # ====================
423  # ... Command line arguments ...
424  # ------------------------------
425  args = get_arguments()
426
427  print(os.getcwd())
428  print(os.path.abspath(__file__))
429
430  # ... Turn interactive mode off ...
431  # ---------------------------------
432  if not args.show:
433    import matplotlib
434    matplotlib.use('Agg')
435
436  import matplotlib.pyplot as plt
437  # from matplotlib.backends.backend_pdf import PdfPages
438
439  if not args.show:
440    plt.ioff()
441
442  # ... Variables and constants ...
443  # -------------------------------
444  delay = 60  # 2 months delay
445
446  # ... Files and directories ...
447  # -----------------------------
448  config_file = os.path.join(
449    "card",
450    "config_{}_{}.ini".format(args.center, args.project)
451  )
452
453  if not os.path.isfile(config_file):
454    print("File {} missing ".format(config_file))
455    exit(9)
456
457  (DIR, DODS) = parse_config(config_file)
458
459  # (file_param, file_utheo, file_data) = \
460  #     get_input_files(DIR["SAVEDATA"],
461  #                     [OUT["PARAM"], OUT["UTHEO"], OUT["BILAN"]])
462
463  img_name = "{}_{}_{}".format(
464    os.path.splitext(
465      os.path.basename(__file__)
466    )[0].replace("plot_", ""),
467    args.project,
468    args.center,
469  )
470
471  conn, cursor = cdb.connect_db(
472    db_data.db_host,
473    db_data.db_name,
474    db_data.db_user,
475    db_data.db_pwd,
476  )
477
478  today = dt.date.today()
479
480  if args.verbose:
481    fmt_str = "{:10s} : {}"
482    print(fmt_str.format("args", args))
483    print(fmt_str.format("today", today))
484    print(fmt_str.format("img_name", img_name))
485    print("")
486
487  # .. Range of dates ..
488  # ====================
489  if args.full:
490    (date_min, date_max) = (
491      dt.date(today.year, 1, 1),
492      dt.date(today.year, 12, 31),
493    )
494  elif args.range:
495    (date_min, date_max) = (args.range)
496  else:
497    (date_min, date_max) = (
498      dt.date(today.year, 1, 1),
499      dt.date(today.year, 12, 31),
500    )
501  bilan_plot = PlotData(date_min, date_max)
502  bilan_plot.faitle = today
503
504  # .. Get project info ..
505  # ======================
506  table_name = "conso.tbl_allocation"
507  request = (
508    "SELECT * "
509    "FROM " + table_name + " "
510    "WHERE project = '" + args.project + "' "
511    "ORDER BY start_date"
512    ";"
513  )
514
515  cdb.select_db(cursor, request)
516  # print(cursor.rowcount)
517  # print(cursor.fetchall())
518  # print(cursor.description)
519
520  projet = Project(args.project, args.center)
521  for row in cursor:
522    if (bilan_plot.date_min >= row["start_date"].date() and
523        bilan_plot.date_min <= row["end_date"].date()) or \
524       (bilan_plot.date_max >= row["start_date"].date() and
525        bilan_plot.date_max <= row["end_date"].date()) or \
526       (bilan_plot.date_min <= row["start_date"].date() and
527        bilan_plot.date_max >= row["end_date"].date()):
528      projet.add_alloc(
529        row["id"],
530        row["machine"],
531        row["node_type"],
532        row["start_date"],
533        row["end_date"],
534        row["total_hrs"],
535      )
536
537  # .. Fill in data ..
538  # ==================
539  # ... Initialization ...
540  # ----------------------
541
542  bilan = DataDict()
543  bilan.init_range(projet.start_date.date(), projet.end_date.date())
544
545  # ... Extract data from table ...
546  # -------------------------------
547  bilan.fill_data(projet)
548
549  # .. Extract data depending on C.L. arguments ..
550  # ==============================================
551  if args.full:
552    selected_items = bilan.get_items_in_full_range(args.inc)
553  elif args.range:
554    selected_items = bilan.get_items_in_range(
555      args.range[0], args.range[1], args.inc
556    )
557  else:
558    selected_items = bilan.get_items(args.inc)
559
560  last_filled_date = max([
561    item.date for item in selected_items
562    if item.filled
563  ])
564
565  # .. Compute data to be plotted ..
566  # ================================
567  nb_items = len(selected_items)
568
569  xcoord = np.linspace(1, nb_items, num=nb_items)
570  dates = [item.date for item in selected_items]
571
572  projet.get_theo_eq(dates)
573
574  cumul = np.array([item.conso for item in selected_items],
575                        dtype=float)
576  consos = []
577  consos.append(cumul[0])
578  consos[1:nb_items] = cumul[1:nb_items] - cumul[0:nb_items-1]
579  consos = np.array(consos, dtype=float)
580
581  real_uses = np.array(
582    [100.*item.real_use for item in selected_items],
583    dtype=float
584  )
585
586  theo_uses = []
587  real_uses2 = []
588  for idx, date in enumerate(dates):
589    for item in projet.alloc_items:
590      if date >= item.start_date.date() and \
591         date <= item.end_date.date():
592        poly_theo  = item.theo_eq
593        alloc_val = item.alloc
594        break
595    theo_uses.append(100. * poly_theo(dates.index(date)))
596    real_uses2.append((100 * cumul[idx]) / alloc_val)
597  delay_uses = delay * [0., ] + theo_uses[:-delay]
598  theo_uses = np.array(theo_uses, dtype=float)
599  delay_uses = np.array(delay_uses, dtype=float)
600
601  run_mean = np.array(
602    [item.run_mean for item in selected_items],
603    dtype=float
604  )
605  pen_mean = np.array(
606    [item.pen_mean for item in selected_items],
607    dtype=float
608  )
609  run_std  = np.array(
610    [item.run_std for item in selected_items],
611    dtype=float
612  )
613  pen_std  = np.array(
614    [item.pen_std for item in selected_items],
615    dtype=float
616  )
617
618  # .. Plot stuff ..
619  # ================
620  # ... Initialize figure ...
621  # -------------------------
622  (fig, ax_conso, ax_theo) = plot_init()
623
624  # ... Plot data ...
625  # -----------------
626  plot_data(
627    ax_conso, ax_theo, xcoord, dates,
628    consos, real_uses, theo_uses, delay_uses,
629    run_mean, pen_mean, run_std, pen_std
630  )
631
632  # ... Tweak figure ...
633  # --------------------
634  title = "Consommation {})".format(
635    projet.project.upper(),
636  )
637  faitle = "(le {:%d/%m/%Y})".format(
638    today,
639  )
640
641  plot_config(
642    fig, ax_conso, ax_theo,
643    xcoord, dates, real_uses[dates.index(last_filled_date)],
644    title, faitle, projet, bilan_plot
645  )
646
647  # ... Save figure ...
648  # -------------------
649  if args.range:
650    img_out = os.path.join(
651      DIR["PLOT"], args.center, args.project,
652      "{}_{:%Y%m%d}_{:%Y%m%d}.pdf".format(img_name, *args.range)
653    )
654  else:
655    img_out = os.path.join(
656      DIR["PLOT"], args.center, args.project,
657      "{}_{:%Y%m%d}.pdf".format(img_name, today)
658    )
659  if args.verbose:
660    print("Save image as {}".format(img_out))
661
662  plot_save(img_out, title)
663
664  # ... Publish figure on dods ...
665  # ------------------------------
666  if args.dods:
667    if args.verbose:
668      print("Publish figure on dods")
669    dods_cp(img_out, img_name, DODS)
670
671  # if args.show:
672  #   plt.show()
673
674  exit(0)
Note: See TracBrowser for help on using the repository browser.