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

Last change on this file since 4035 was 3083, checked in by labetoulle, 7 years ago

Overall update (typos, get_project_list.py, ...)

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