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

Last change on this file since 2775 was 2775, checked in by labetoulle, 6 years ago

Overall cleaning and refactoring

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