source: TOOLS/ConsoGENCMIP6/bin/plot_bilan.py @ 2517

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

Add to bilan a line that shows the theoratical two months under-consumption

  • Property svn:executable set to *
File size: 13.8 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      for date in dates:
122        self.poly_theo = poly_theo
123        self.poly_delay = poly_delay
124
125  #---------------------------------------
126  def get_items_in_range(self, date_beg, date_end, inc=1):
127    """
128    """
129    items = (item for item in self.itervalues()
130                   if item.date >= date_beg and
131                      item.date <= date_end)
132    items = sorted(items, key=lambda item: item.date)
133
134    return items[::inc]
135
136  #---------------------------------------
137  def get_items_in_full_range(self, inc=1):
138    """
139    """
140    items = (item for item in self.itervalues())
141    items = sorted(items, key=lambda item: item.date)
142
143    return items[::inc]
144
145  #---------------------------------------
146  def get_items(self, inc=1):
147    """
148    """
149    items = (item for item in self.itervalues()
150                   if item.isfilled())
151    items = sorted(items, key=lambda item: item.date)
152
153    return items[::inc]
154
155
156class Conso(object):
157  #---------------------------------------
158  def __init__(self, date, conso=np.nan,
159               real_use=np.nan, theo_use=np.nan,
160               run_mean=np.nan, pen_mean=np.nan,
161               run_std=np.nan, pen_std=np.nan):
162    self.date     = date
163    self.conso    = conso
164    self.real_use = real_use
165    self.theo_use = theo_use
166    # self.theo_equ = np.nan
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 = plt.subplots(figsize=(paper_size/2.54))
190  ax_theo = ax_conso.twinx()
191
192  return fig, ax_conso, ax_theo
193
194
195########################################
196def plot_data(ax_conso, ax_theo, xcoord, dates,
197              consos, theo_uses, real_uses, theo_equs, theo_delay,
198              run_mean, pen_mean, run_std, pen_std):
199  """
200  """
201  line_style = "-"
202  if args.full:
203    line_width = 0.05
204  else:
205    # line_style = "+-"
206    line_width = 0.1
207
208  ax_conso.bar(
209    xcoord, consos, width=1, align="center", color="linen",
210    linewidth=line_width, label="conso (heures)"
211  )
212
213  ax_theo.plot(
214    xcoord, real_uses, line_style,
215    color="forestgreen", linewidth=1, markersize=8,
216    solid_capstyle="round", solid_joinstyle="round",
217    label="conso\nréelle (%)"
218  )
219  ax_theo.plot(
220    xcoord, theo_equs, "--",
221    color="firebrick", linewidth=0.5,
222    solid_capstyle="round", solid_joinstyle="round"
223  )
224  ax_theo.plot(
225    xcoord, theo_uses, line_style,
226    color="firebrick", linewidth=1, markersize=8,
227    solid_capstyle="round", solid_joinstyle="round",
228    label="conso\nthéorique (%)"
229  )
230  ax_theo.plot(
231    xcoord, theo_delay, ":",
232    color="firebrick", linewidth=0.5,
233    solid_capstyle="round", solid_joinstyle="round",
234    label="retard de\ndeux mois (%)"
235  )
236
237
238########################################
239def plot_config(fig, ax_conso, ax_theo, xcoord, dates, title,
240                conso_per_day):
241  """
242  """
243  # ... Config axes ...
244  # -------------------
245  # 1) Range
246  conso_max = np.nanmax(consos)
247  if args.max:
248    ymax = conso_max  # + conso_max*.1
249  else:
250    ymax = 2. * conso_per_day
251
252  if conso_max > ymax:
253    ax_conso.annotate(
254      "{:.2e} heures".format(conso_max),
255      ha="left",
256      va="top",
257      fontsize="xx-small",
258      bbox=dict(boxstyle="round", fc="w", ec="0.5", color="gray",),
259      xy=(np.nanargmax(consos)+1.2, ymax),
260      textcoords="axes fraction",
261      xytext=(0.01, 0.9),
262      arrowprops=dict(
263        arrowstyle="->",
264        shrinkA=0,
265        shrinkB=0,
266        color="gray",
267      ),
268    )
269
270  xmin, xmax = xcoord[0]-1, xcoord[-1]+1
271  ax_conso.set_xlim(xmin, xmax)
272  ax_conso.set_ylim(0., ymax)
273  ax_theo.set_ylim(0., 100)
274
275  # 2) Ticks labels
276  (date_beg, date_end) = (dates[0], dates[-1])
277  date_fmt = "{:%d-%m}"
278
279  if date_end - date_beg > dt.timedelta(weeks=9):
280    maj_xticks = [x for x, d in zip(xcoord, dates)
281                     if d.weekday() == 0]
282    maj_xlabs  = [date_fmt.format(d) for d in dates
283                     if d.weekday() == 0]
284  else:
285    maj_xticks = [x for x, d in zip(xcoord, dates)]
286    maj_xlabs  = [date_fmt.format(d) for d in dates]
287
288  ax_conso.ticklabel_format(axis="y", style="sci", scilimits=(0, 0))
289
290  ax_conso.set_xticks(xcoord, minor=True)
291  ax_conso.set_xticks(maj_xticks, minor=False)
292  ax_conso.set_xticklabels(
293    maj_xlabs, rotation="vertical", size="x-small"
294  )
295
296  yticks = list(ax_conso.get_yticks())
297  yticks.append(conso_per_day)
298  ax_conso.set_yticks(yticks)
299
300  ax_theo.spines["right"].set_color("firebrick")
301  ax_theo.tick_params(colors="firebrick")
302  ax_theo.yaxis.label.set_color("firebrick")
303
304  ax_conso.axhline(y=conso_per_day, color="blue", alpha=0.5,
305                   label="conso journaliÚre\nidéale (heures)")
306
307  for x, d in zip(xcoord, dates):
308    if d.weekday() == 0 and d.hour == 0:
309      ax_conso.axvline(x=x, color="black", alpha=0.5,
310                       linewidth=0.5, linestyle=":")
311
312  # 3) Define axes title
313  for ax, label in (
314    (ax_conso, "heures"),
315    (ax_theo, "%"),
316  ):
317    ax.set_ylabel(label, fontweight="bold")
318    ax.tick_params(axis="y", labelsize="small")
319
320  # 4) Define plot size
321  fig.subplots_adjust(
322    left=0.08,
323    bottom=0.09,
324    right=0.93,
325    top=0.93,
326  )
327
328  # ... Main title and legend ...
329  # -----------------------------
330  fig.suptitle(title, fontweight="bold", size="large")
331  for ax, loc in (
332    (ax_conso, "upper left"),
333    (ax_theo, "upper right"),
334  ):
335    ax.legend(loc=loc, fontsize="x-small", frameon=False)
336
337
338########################################
339def get_arguments():
340  parser = ArgumentParser()
341  parser.add_argument("-v", "--verbose", action="store_true",
342                      help="verbose mode")
343  parser.add_argument("-f", "--full", action="store_true",
344                      help="plot the whole period")
345  parser.add_argument("-i", "--increment", action="store",
346                      type=int, default=1, dest="inc",
347                      help="sampling increment")
348  parser.add_argument("-r", "--range", action="store", nargs=2,
349                      type=string_to_date,
350                      help="date range: ssaa-mm-jj ssaa-mm-jj")
351  parser.add_argument("-m", "--max", action="store_true",
352                      help="plot with y_max = allocation")
353  parser.add_argument("-s", "--show", action="store_true",
354                      help="interactive mode")
355  parser.add_argument("-d", "--dods", action="store_true",
356                      help="copy output on dods")
357
358  return parser.parse_args()
359
360
361########################################
362if __name__ == '__main__':
363
364  # .. Initialization ..
365  # ====================
366  # ... Command line arguments ...
367  # ------------------------------
368  args = get_arguments()
369  if args.verbose:
370    print(args)
371
372  # ... Turn interactive mode off ...
373  # ---------------------------------
374  if not args.show:
375    import matplotlib
376    matplotlib.use('Agg')
377
378  import matplotlib.pyplot as plt
379  # from matplotlib.backends.backend_pdf import PdfPages
380
381  if not args.show:
382    plt.ioff()
383
384  # ... Files and directories ...
385  # -----------------------------
386  project_name, DIR, OUT = parse_config("bin/config.ini")
387
388  (file_param, file_utheo, file_data) = \
389      get_input_files(DIR["SAVEDATA"],
390                      [OUT["PARAM"], OUT["UTHEO"], OUT["BILAN"]])
391
392  img_name = "bilan"
393  today = os.path.basename(file_param).strip(OUT["PARAM"])
394
395  if args.verbose:
396    print(file_param)
397    print(file_utheo)
398    print(file_data)
399    print(img_name)
400    print(today)
401
402  # .. Get project info ..
403  # ======================
404  projet = Project(project_name)
405  projet.fill_data(file_param)
406  projet.get_date_init(file_utheo)
407
408  # .. Fill in data ..
409  # ==================
410  # ... Initialization ...
411  # ----------------------
412  bilan = DataDict()
413  bilan.init_range(projet.date_init, projet.deadline)
414  # ... Extract data from file ...
415  # ------------------------------
416  bilan.fill_data(file_data)
417  # ... Compute theoratical use from known data  ...
418  # ------------------------------------------------
419  bilan.theo_equation()
420
421  # .. Extract data depending on C.L. arguments ..
422  # ==============================================
423  if args.full:
424    selected_items = bilan.get_items_in_full_range(args.inc)
425  elif args.range:
426    selected_items = bilan.get_items_in_range(
427      args.range[0], args.range[1], args.inc
428    )
429  else:
430    selected_items = bilan.get_items(args.inc)
431
432  # .. Compute data to be plotted ..
433  # ================================
434  nb_items = len(selected_items)
435
436  xcoord    = np.linspace(1, nb_items, num=nb_items)
437  dates   = [item.date for item in selected_items]
438
439  cumul     = np.array([item.conso for item in selected_items],
440                        dtype=float)
441  consos    = []
442  consos.append(cumul[0])
443  consos[1:nb_items] = cumul[1:nb_items] - cumul[0:nb_items-1]
444  consos    = np.array(consos, dtype=float)
445
446  conso_per_day = projet.alloc / projet.days
447
448  theo_uses = np.array([100.*item.theo_use for item in selected_items],
449                       dtype=float)
450
451  real_uses = np.array([100.*item.real_use for item in selected_items],
452                       dtype=float)
453  theo_equs = np.array(
454    [100. * bilan.poly_theo(date.timetuple().tm_yday)
455      for date in dates],
456    dtype=float
457  )
458  theo_delay = np.array(
459    [100. * bilan.poly_delay(date.timetuple().tm_yday)
460      for date in dates],
461    dtype=float
462  )
463
464  run_mean = np.array([item.run_mean for item in selected_items],
465                       dtype=float)
466  pen_mean = np.array([item.pen_mean for item in selected_items],
467                       dtype=float)
468  run_std  = np.array([item.run_std for item in selected_items],
469                       dtype=float)
470  pen_std  = np.array([item.pen_std for item in selected_items],
471                       dtype=float)
472
473  # .. Plot stuff ..
474  # ================
475  # ... Initialize figure ...
476  # -------------------------
477  (fig, ax_conso, ax_theo) = plot_init()
478
479  # ... Plot data ...
480  # -----------------
481  plot_data(ax_conso, ax_theo, xcoord, dates,
482            consos, theo_uses, real_uses, theo_equs, theo_delay,
483            run_mean, pen_mean, run_std, pen_std)
484
485  # ... Tweak figure ...
486  # --------------------
487  title = "Consommation {}\n({:%d/%m/%Y} - {:%d/%m/%Y})".format(
488    projet.project.upper(),
489    projet.date_init,
490    projet.deadline
491  )
492
493  plot_config(
494    fig, ax_conso, ax_theo, xcoord, dates, title, conso_per_day
495  )
496
497  # ... Save figure ...
498  # -------------------
499  img_in  = os.path.join(DIR["PLOT"], "{}.pdf".format(img_name))
500  img_out = os.path.join(DIR["SAVEPLOT"],
501                         "{}_{}.pdf".format(img_name, today))
502
503  plot_save(img_in, img_out, title, DIR)
504
505  # ... Publish figure on dods ...
506  # ------------------------------
507  if args.dods:
508    dods_cp(img_in, DIR)
509
510  if args.show:
511    plt.show()
512
513  exit(0)
Note: See TracBrowser for help on using the repository browser.