"""
The ANDES plotting tool.
"""
import logging
import math
import os
import re
import numpy as np
from andes.core.var import BaseVar
from andes.shared import which, mpl, plt
from andes.utils.paths import get_dot_andes_path # NOQA
logger = logging.getLogger(__name__)
DPI = None
[docs]def set_latex():
"""
Enables LaTeX for matplotlib based on the `with_latex` option and `dvipng` availability.
Returns
-------
bool
True for LaTeX on, False for off
"""
if which('dvipng'):
mpl.rc('text', usetex=True)
no_warn_file = os.path.join(get_dot_andes_path(), '.no_warn_latex')
if not os.path.isfile(no_warn_file):
print('Using LaTeX for rendering. If an error occurs:')
print('a) If you are using `andes plot`, disable with option "-d",')
print('b) If you are using `plot()`, set "latex=False".')
try:
with open(os.path.join(get_dot_andes_path(), '.no_warn_latex'), 'w') as f:
f.write('0')
except OSError:
pass
return True
return False
[docs]def set_font(family='serif', size=12, style='normal', weight='normal'):
"""
Sets the font for matplotlib.
Parameters
----------
family : str
Font family.
size : int
Font size.
style : str
Font style.
weight : str
Font weight.
"""
mpl.rc('font', family=family, size=size, style=style, weight=weight)
[docs]def set_style(style='default'):
"""
Set matplotlib style.
Parameters
----------
style : str
`default`, `ieee` (require `scienceplots`), or other available styles
(see `matplotlib.pyplot.style.available`).
"""
if style is None:
style = 'default'
if style == 'ieee':
try:
plt.style.use(['science', 'ieee'])
except (NameError, OSError):
logger.error("Please install `scienceplots` with `pip` to use the 'ieee' style.")
else:
plt.style.use(style)
set_font()
[docs]class TDSData:
"""
A data container for loading and plotting results from Andes time-domain simulation.
"""
[docs] def __init__(self, full_name=None, mode='file', dae=None, path=None):
# paths and file names
self._mode = mode
self.full_name = full_name
self.dae = dae
self._path = path if path else os.getcwd()
self.file_name = None
self.file_ext = None
self._npy_file = None
self._lst_file = None
# data members for raw data
self._idx = [] # indices of variables
self._uname = [] # unformatted variable names
self._fname = [] # formatted variable names
self._data = [] # data loaded from file
# auxillary data members for fast query
self.t = []
self.nvars = 0 # total number of variables including `t`
if self._mode == 'file':
self._process_names()
self.load_lst()
self.load_npy_or_csv()
elif self._mode == 'memory':
self.load_dae()
self._process_names()
else:
raise NotImplementedError(f'Unknown mode {self._mode}.')
self._process_names()
def _process_names(self):
"""
Helper function to process the file names.
Use "Untitled" if name is not set for case file.
"""
if self.full_name is None:
logger.info("Input file name not detected. Using `Untitled`.")
self.full_name = 'Untitled'
self.file_name = 'Untitled'
self.file_ext = ''
else:
self.file_name, self.file_ext = os.path.splitext(self.full_name)
self._npy_file = os.path.join(self._path, self.file_name + '.npy')
npz_path = os.path.join(self._path, self.file_name + '.npz')
if os.path.isfile(npz_path):
self._npy_file = npz_path
self._lst_file = os.path.join(self._path, self.file_name + '.lst')
self._csv_file = os.path.join(self._path, self.file_name + '.csv')
[docs] def load_dae(self):
"""
Load from DAE time series.
"""
dae = self.dae
system = self.dae.system
self.t = dae.ts.t
if system.Output.n == 0:
self.nvars = dae.n + dae.m + dae.o + 1
else:
self.nvars = len(system.Output.xidx) + len(system.Output.yidx) + dae.o + 1
self._uname = ['Time [s]'] + dae.x_name_output + dae.y_name_output + dae.z_name
self._fname = ['Time [s]'] + dae.x_tex_name_output + dae.y_tex_name_output + dae.z_tex_name
self._idx = list(range(self.nvars))
if dae.system.files.lst is not None:
self.full_name = dae.system.files.lst
else:
self.full_name = dae.system.files.case
if len(self.t) > 0:
self._data = dae.ts.txyz
[docs] def load_lst(self):
"""
Load the lst file into internal data structures `_idx`, `_fname`, `_uname`, and counts the number of
variables to `nvars`.
Returns
-------
None
"""
with open(self._lst_file, 'r') as fd:
lines = fd.readlines()
idx, uname, fname = list(), list(), list()
for line in lines:
values = line.split(',')
values = [x.strip() for x in values]
# preserve the idx ordering here in case variables are not
# ordered by idx
idx.append(int(values[0])) # convert to integer
uname.append(values[1])
fname.append(values[2])
self._idx = idx
self._fname = fname
self._uname = uname
self.nvars = len(uname)
[docs] def find(self, query, exclude=None, formatted=False, idx_only=False):
"""
Return variable names and indices matching `query`.
Parameters
----------
query : str
The string for querying variables. Multiple conditions can be separated by comma without space.
exclude : str, optional
A string pattern to be excluded
formatted : bool, optional
True to return formatted names, False otherwise
idx_only : bool, optional
True if only return indices
Returns
-------
(list, list)
(List of found indices, list of found names)
"""
# load the variable list to search in
names = self._uname if formatted is False else self._fname
found_idx, found_names = list(), list()
query_list = query.split(',')
for idx, name in zip(self._idx, names):
for q in query_list:
if re.search(q, name):
if exclude and re.search(exclude, name):
continue
found_idx.append(idx)
found_names.append(name)
if idx_only:
return found_idx
else:
return found_idx, found_names
[docs] def load_npy_or_csv(self, delimiter=','):
"""
Load the npy, zpy or (the legacy) csv file into the
internal data structure `self._xy`.
Parameters
----------
delimiter : str, optional
The delimiter for the case file. Default to comma.
Returns
-------
None
"""
try:
data = np.load(self._npy_file)
if self._npy_file.endswith('npz'):
data = data['data']
except FileNotFoundError:
data = np.loadtxt(self._csv_file, delimiter=delimiter, skiprows=1)
self._data = data
[docs] def get_values(self, idx):
"""
Return the variable values at the given indices.
Parameters
----------
idx : list
The indicex of the variables to retrieve. `idx=0` is for Time. Variable indices start at 1.
Returns
-------
np.ndarray
Variable data
"""
return self._data[:, idx]
[docs] def export_csv(self, path=None, idx=None, header=None, formatted=False,
sort_idx=True, fmt='%.18e'):
"""
Export to a csv file.
Parameters
----------
path : str
path of the csv file to save
idx : None or array-like, optional
the indices of the variables to export. Export all by default
header : None or array-like, optional
customized header if not `None`. Use the names from the lst file
by default
formatted : bool, optional
Use LaTeX-formatted header. Does not apply when using customized
header
sort_idx : bool, optional
Sort by idx or not, # TODO: implement sort
fmt : str
cell formatter
Returns
-------
str
The path of the exported csv file
"""
if not path:
path = self._csv_file
if not idx:
idx = self._idx
if not header:
header = self.get_header(idx, formatted=formatted)
if len(idx) != len(header):
raise ValueError("Idx length does not match header length")
body = self.get_values(idx)
with open(path, 'w') as fd:
fd.write(','.join(header) + '\n')
np.savetxt(fd, body, fmt=fmt, delimiter=',')
logger.info(f'CSV data saved to "{path}".')
return path
def _process_yidx(self, yidx, a):
"""
Helper function for processing ``yidx`` if it is a ``BaseVar`` or a list
of BaseVars.
Indexing by ``a`` is considered.
"""
dae = self.dae
system = self.dae.system
if isinstance(yidx, BaseVar):
yidx = [yidx]
if isinstance(yidx, list) and isinstance(yidx[0], BaseVar):
all_yidx = np.array([], dtype=int)
for item in yidx:
if item.n == 0:
logger.info("Parent model <%s> of variable <%s> contains no device, ignored.",
item.owner.class_name, item.name)
continue
if system.Output.n > 0:
output_addr = system.Output.to_output_addr(item, check=True)
if len(output_addr) == 0:
continue
nx = len(system.Output.xidx)
else:
output_addr = item.a
nx = dae.n
# states are offset by 1 for Time. Algebs are offset by 1 + nx
if item.v_code == 'y':
offset = nx + 1
else:
offset = 1
new_yidx = output_addr + offset
if a is not None:
new_yidx = np.take(new_yidx, a)
all_yidx = np.append(all_yidx, new_yidx)
yidx = all_yidx
elif isinstance(yidx, int):
yidx = [yidx]
# a list of integers will remain unchanged
return yidx
[docs] def plot(self, yidx, xidx=(0,), *, a=None, ytimes=None, ycalc=None,
left=None, right=None, ymin=None, ymax=None,
xlabel=None, ylabel=None, xheader=None, yheader=None,
legend=None, grid=False, greyscale=False, latex=True,
dpi=DPI, line_width=1.0, font_size=12, savefig=None, save_format=None, show=True,
title=None, linestyles=None, use_bqplot=False,
hline1=None, hline2=None, vline1=None, vline2=None, hline=None, vline=None,
fig=None, ax=None, backend=None,
set_xlim=True, set_ylim=True, autoscale=False,
legend_bbox=None, legend_loc=None, legend_ncol=1,
figsize=None, color=None,
**kwargs):
"""
Entry function for plotting.
This function retrieves the x and y values based on the `xidx` and
`yidx` inputs, applies scaling functions `ytimes` and `ycalc` sequentially,
and delegates the plotting to the backend.
Parameters
----------
yidx : list or int
The indices for the y-axis variables
xidx : tuple or int, optional
The index for the x-axis variable
a : tuple or list, optional
The 0-indexed sub-indices into `yidx` to plot.
ytimes : float, optional
A scaling factor to apply to all y values.
left : float
The starting value of the x axis
right : float
The ending value of the x axis
ymin : float
The minimum value of the y axis
ymax : float
The maximum value of the y axis
ylabel : str
Text label for the y axis
yheader : list
A list containing the variable names for the y-axis variable
title : str
Title string to be shown at the top
fig
Existing figure object to draw the axis on.
ax
Existing axis object to draw the lines on.
Other Parameters
----------------
ycalc: callable, optional
A callable to apply to all y values after scaling with `ytimes`.
xlabel : str
Text label for the x axis
xheader : list
A list containing the variable names for the x-axis variable
legend : bool
True to show legend and False otherwise
legend_ncol : int
Number of columns in legend
legend_bbox : tuple of two floats
legend box to anchor
grid : bool
True to show grid and False otherwise
latex : bool
True to enable latex and False to disable
greyscale : bool
True to use greyscale, False otherwise
savefig : bool or str
True to save to png figure file.
str is treated as the output file name.
save_format : str
File extension string (pdf, png or jpg) for the savefig format
dpi : int
Dots per inch for screen print or save.
`savefig` uses a minimum of 200 dpi
line_width : float
Plot line width
font_size : float
Text font size (labels and legends)
figsize : tuple
Figure size passed when creating new figure
show : bool
True to show the image
backend : str or None
`bqplot` to use the bqplot backend in notebook.
None for matplotlib.
hline1: float, optional
Dashed horizontal line 1
hline2: float, optional
Dashed horizontal line 2
vline1: float, optional
Dashed horizontal line 1
vline2: float, optional
Dashed vertical line 2
hline: float or Iterable
y-axis location of horizontal line(s)
vline: float or Iterable
x-axis location of vertical line(s)
Returns
-------
(fig, ax)
Figure and axis handles for matplotlib backend.
fig
Figure object for bqplot backend.
"""
if self._mode == 'memory':
yidx = self._process_yidx(yidx, a)
else: # file mode
if a is not None:
yidx = np.take(yidx, a)
if len(yidx) == 0:
logger.error("No variables to plot.")
return
xvalue = self.get_values(xidx)
yvalue = self.get_values(yidx)
# header: names for variables
# axis labels: the texts next to axes
if not xheader:
xheader = self.get_header(xidx, formatted=latex)
if not yheader:
yheader = self.get_header(yidx, formatted=latex)
# process `ytimes`
if ytimes is not None:
ytimes = float(ytimes)
if ytimes != 1.0:
yvalue = _scale_func(ytimes)(yvalue)
# call `ycalc` on `yvalue`
if ycalc is not None:
yvalue = ycalc(yvalue)
plot_call = self.get_call(backend)
return plot_call(xdata=xvalue, ydata=yvalue,
left=left, right=right, ymin=ymin, ymax=ymax,
xheader=xheader, yheader=yheader, xlabel=xlabel, ylabel=ylabel,
legend=legend, grid=grid, greyscale=greyscale, latex=latex,
dpi=dpi, line_width=line_width, font_size=font_size,
savefig=savefig, save_format=save_format, show=show, title=title,
hline1=hline1, hline2=hline2, vline1=vline1, vline2=vline2,
fig=fig, ax=ax, linestyles=linestyles,
set_xlim=set_xlim, set_ylim=set_ylim, autoscale=autoscale,
legend_bbox=legend_bbox, legend_loc=legend_loc, legend_ncol=legend_ncol,
figsize=figsize, hline=hline, vline=vline, color=color,
**kwargs)
[docs] def get_call(self, backend=None):
"""
Get the internal `plot_data` function for the specified backend.
"""
if backend == 'bqplot':
return self.bqplot_data
return self.plot_data
[docs] def data_to_df(self):
"""Convert to pandas.DataFrame"""
pass
[docs] def guess_event_time(self):
"""
Guess the event starting time from the input data by checking
when the values start to change
"""
pass
[docs] def bqplot_data(self, xdata, ydata, *, xheader=None, yheader=None, xlabel=None, ylabel=None,
left=None, right=None, ymin=None, ymax=None, legend=True, grid=False, fig=None,
dpi=DPI, line_width=1.0, greyscale=False, savefig=None, save_format=None,
title=None,
**kwargs):
"""
Plot with ``bqplot``. Experimental and incomplete.
"""
from bqplot import pyplot as plt
if not isinstance(ydata, np.ndarray):
raise TypeError("ydata must be numpy array. Retrieve with `get_values()`.")
if ydata.ndim == 1:
ydata = ydata.reshape((-1, 1))
if fig is None:
fig = plt.figure(dpi=dpi)
plt.plot(xdata, ydata.transpose(),
linewidth=line_width,
figure=fig,
)
if yheader:
plt.label(yheader)
if title:
plt.title(title)
plt.show()
return fig
[docs] def plot_data(self, xdata, ydata, *, xheader=None, yheader=None, xlabel=None, ylabel=None, linestyles=None,
left=None, right=None, ymin=None, ymax=None, legend=None, grid=False, fig=None, ax=None,
latex=True, dpi=DPI, line_width=1.0, font_size=12, greyscale=False, savefig=None,
save_format=None, show=True, title=None,
hline1=None, hline2=None, vline1=None, hline=None, vline=None,
vline2=None, set_xlim=True, set_ylim=True, autoscale=False, figsize=None,
legend_bbox=None, legend_loc=None, legend_ncol=1,
mask=True, color=None, style='default',
**kwargs):
"""
Plot lines for the supplied data and options.
This functions takes `xdata` and `ydata` values.
If you provide variable indices instead of values, use `plot()`.
See the argument lists of `plot()` for more.
Parameters
----------
xdata : array-like
An array-like object containing the values for the x-axis variable
ydata : array
An array containing the values of each variables for the y-axis variable. The row
of `ydata` must match the row of `xdata`. Each column correspondings to a variable.
mask : bool
If enabled (1), when specifying axis limits, only data in the limits will be
used for plotting to optimize for autoscaling.
It is done through an index mask.
Returns
-------
(fig, ax)
The figure and axis handles
Examples
--------
To plot the results of arithmetic calculation of variables, retrieve the values,
do the calculation, and plot with `plot_data`.
>>> v = ss.dae.ts.y[:, ss.PVD1.v.a]
>>> Ipcmd = ss.dae.ts.y[:, ss.PVD1.Ipcmd_y.a]
>>> t = ss.dae.ts.t
>>> ss.TDS.plt.plot_data(t, v * Ipcmd,
>>> xlabel='Time [s]',
>>> ylabel='Ipcmd [pu]')
"""
set_style(style)
if not isinstance(ydata, np.ndarray):
raise TypeError("ydata must be a numpy array. Retrieve with get_values().")
if ydata.ndim == 1:
ydata = ydata.reshape((-1, 1))
n_lines = ydata.shape[1]
if latex:
set_latex()
if isinstance(color, (str, float, int)):
color = [color] * n_lines
elif color is None:
color = [None] * n_lines
# set default x min based on simulation time
if not left:
left = xdata[0] - 1e-6
if not right:
right = xdata[-1] + 1e-6
if not linestyles:
linestyles = ['-', '--', '-.', ':']
linestyles = linestyles * int(n_lines / len(linestyles) + 1)
if fig is None or ax is None:
fig = plt.figure(dpi=dpi, figsize=figsize)
ax = plt.gca()
if greyscale:
plt.gray()
if mask is True:
mask = (xdata >= (left - 0.1)) & (xdata <= (right + 0.1))
xdata = xdata[mask]
ydata = ydata[mask.reshape(-1, )]
for i in range(n_lines):
ax.plot(xdata,
ydata[:, i],
ls=linestyles[i],
label=yheader[i] if yheader else None,
linewidth=line_width,
color=color[i],
)
if xlabel is not None:
ax.set_xlabel(xlabel)
elif xheader is not None and len(xheader) > 0:
ax.set_xlabel(xheader[0])
if ylabel:
ax.set_ylabel(ylabel)
ax.ticklabel_format(useOffset=False)
if set_xlim is True:
ax.set_xlim(left=left, right=right)
if set_ylim is True:
ax.set_ylim(bottom=ymin, top=ymax)
if autoscale is True:
ax.autoscale(axis='y')
if grid:
ax.grid(True, linestyle='--')
if yheader is None:
legend = False
elif legend is None:
if len(yheader) <= 8:
legend = True
if legend:
ax.legend(bbox_to_anchor=legend_bbox,
loc=legend_loc,
ncol=legend_ncol,
frameon=False,
)
if title:
ax.set_title(title)
# --- process hlines and vlines
if hline1 or hline2 or vline1 or vline2:
logger.warning("hline1, hline2, vline1, and vline2 are deprecated. Use `hline` and `vline`.")
if isinstance(hline, (float, int, np.floating, np.integer)):
hline = [hline]
elif isinstance(hline, tuple):
hline = list(hline)
elif hline is None:
hline = []
if isinstance(vline, (float, int, np.floating, np.integer)):
vline = [vline]
elif isinstance(vline, tuple):
vline = list(vline)
elif vline is None:
vline = []
# process the legacy hline1, hline2, and vline1 and vline2
if hline1:
hline.append(hline1)
if hline2:
hline.append(hline2)
if vline1:
vline.append(vline1)
if vline2:
vline.append(vline2)
for loc in hline:
ax.axhline(y=loc, linewidth=1, ls=':', color='grey')
for loc in vline:
ax.axvline(x=loc, linewidth=1, ls=':', color='grey')
# --- hline and vline finished ---
plt.draw()
if savefig:
if save_format is None:
save_format = 'png'
if dpi is None:
dpi = 200
else:
dpi = max(dpi, 200)
# use supplied file name
if isinstance(savefig, str):
outfile = savefig + '.' + save_format
# or generate a new name
else:
count = 1
while True:
outfile = f'{self.file_name}_{count}.{save_format}'
if not os.path.isfile(outfile):
break
count += 1
fig.savefig(outfile, dpi=dpi, bbox_inches='tight')
logger.info('Figure saved to "%s".', outfile)
if show:
plt.show()
return fig, ax
[docs] def plotn(self, nrows: int, ncols: int, yidxes, xidxes=None, *, dpi=DPI, titles=None,
a=None, figsize=None, xlabel=None, ylabel=None, sharex=None, sharey=None, show=True,
xlabel_offs=(0.5, 0.01), ylabel_offs=(0.05, 0.5), hspace=0.2, wspace=0.2,
**kwargs):
"""
Plot multiple subfigures in one figure.
Parameters ``xidxes``, ``a``, ``xlabels`` and ``ylabels``, if provided,
must have the same length as ``yidxes``.
Parameters
----------
nrows : int
number of rows
ncols : int
number of cols
yidx
A list of `BaseVar` or index lists.
"""
nyidxes = len(yidxes)
if nyidxes > nrows * ncols:
raise ValueError("yidxes with length %d does not fit nrows=%d and ncols=%d" %
(nyidxes, nrows, ncols))
fig = plt.figure(figsize=figsize, dpi=dpi)
xidx = (0,)
aidx = None
title = ''
sharex = True if (sharex is None and ncols == 1) else False
sharey = True if (sharey is None and nrows == 1) else False
axes = fig.subplots(nrows, ncols, sharex=sharex, sharey=sharey, squeeze=False)
ii = 0
for jj in range(nrows):
for kk in range(ncols):
if ii >= nyidxes:
break
yidx = yidxes[ii]
ax = axes[jj, kk]
if xidxes is not None:
xidx = xidxes[ii]
if a is not None:
aidx = a[ii]
if titles is not None:
title = titles[ii]
fig, ax = self.plot(yidx, xidx, a=aidx, fig=fig, ax=ax,
xlabel='', ylabel='', title=title,
show=False, **kwargs)
ii += 1
if xlabel:
fig.text(*xlabel_offs, xlabel, ha='center', va='center')
if ylabel:
fig.text(*ylabel_offs, ylabel, ha='center', va='center', rotation='vertical')
fig.subplots_adjust(hspace=hspace, wspace=wspace,)
if show:
plt.show()
return fig, axes
[docs] def panoview(self, mdl, vars, *, ncols=3, idx=None, a=None, figsize=None, **kwargs):
"""
Panoramic view of variables of a given model instance.
Select variables through ``vars``. Select devices through ``idx`` or ``a``,
which has a higher priority.
This function also takes other arguments recognizable by ``self.plot``.
Parameters
----------
mdl : ModelBase
Model instance
var : list of str
A list of variable names to display
ncol : int
Number of columns
idx : list
A list of device idx-es for showing
a : list of int
A list of device 0-based positions for showing
figsize : tuple
Figure size for plotting
Examples
--------
To plot ``omega`` and ``delta`` of GENROUs ``GENROU_1`` and ``GENROU_2``:
.. code-block :: python
system.TDS.plt.plot(system.GENROU,
vars=['omega', 'delta'],
idx=['GENROU_1', 'GENROU_2'])
"""
# `a` takes precedece over `idx`
if a is None:
a = mdl.idx2uid(idx)
# compute the number of rows and cols
states = list()
algebs = list()
states_and_ext = mdl.cache.states_and_ext
algebs_and_ext = mdl.cache.algebs_and_ext
if vars is None:
states = states_and_ext.values()
algebs = algebs_and_ext.values()
else:
for item in vars:
if item in states_and_ext:
states.append(states_and_ext[item])
elif item in algebs_and_ext:
algebs.append(algebs_and_ext[item])
else:
logger.warning("Variable <%s> does not exist in model <%s>",
item, mdl.class_name)
nstates = len(states)
nalgebs = len(algebs)
nrows_states = math.ceil(nstates / ncols)
nrows_algebs = math.ceil(nalgebs / ncols)
# build canvas
if figsize is None:
figsize = (3 * ncols, 2 * (nrows_states + nrows_algebs))
fig, axes = plt.subplots(nrows_states + nrows_algebs, ncols,
figsize=figsize, dpi=DPI, squeeze=False,
)
fig.tight_layout()
# turn off unused axes
if nstates % ncols != 0:
for i in range(nstates % ncols, ncols):
axes[nrows_states-1, i].axis('off')
if nalgebs % ncols != 0:
for i in range(nalgebs % ncols, ncols):
axes[-1, i].axis('off')
# plot states
for ii, item in enumerate(states):
row_no = math.floor(ii / ncols)
col_no = ii % ncols
self.plot(item, a=a,
title=f'${item.tex_name}$',
xlabel='',
fig=fig, ax=axes[row_no, col_no], show=False, **kwargs)
# plot algebs
for ii, item in enumerate(algebs):
row_no = math.floor(ii / ncols) + nrows_states
col_no = ii % ncols
self.plot(item, a=a,
title=f'${item.tex_name}$',
xlabel='',
fig=fig, ax=axes[row_no, col_no], show=False, **kwargs)
return fig, axes
[docs]def parse_y(y, upper, lower=0):
"""
Parse command-line input for Y indices and return a list of indices
Parameters
----------
y : Union[List, Set, Tuple]
Input for Y indices. Could be single item (with or without colon), or
multiple items
upper : int
Upper limit. In the return list y, y[i] <= uppwer.
lower : int
Lower limit. In the return list y, y[i] >= lower.
Returns
-------
"""
if len(y) == 1:
if y[0].count(':') >= 3:
logger.error('Index format not acceptable. Must not contain more than three colons.')
return []
elif y[0].count(':') == 0:
if _isint(y[0]):
y[0] = int(y[0])
return y
elif y[0].count(':') == 1:
if y[0].endswith(':'):
y[0] += str(upper)
if y[0].startswith(':'):
y[0] = str(lower) + y[0]
elif y[0].count(':') == 2:
if y[0].endswith(':'):
y[0] += str(1)
if y[0].startswith(':'):
y[0] = str(lower) + y[0]
if y[0].count('::') == 1:
y[0] = y[0].replace('::', ':{}:'.format(upper))
print(y)
y = y[0].split(':')
for idx, item in enumerate(y[:]):
try:
y[idx] = int(item)
except ValueError:
logger.warning(f'y contains non-numerical values <{item}>. Parsing cannot proceed.')
return []
y_from_range = list(range(*y))
y_in_range = []
for item in y_from_range:
if lower <= item < upper:
y_in_range.append(item)
return y_in_range
else:
y_to_int = []
for idx, val in enumerate(y):
try:
y_to_int.append(int(val))
except ValueError:
logger.warning('Y indices contains non-numerical values. Skipped <{}>.'.format(val))
y_in_range = [item for item in y_to_int if lower <= item < upper]
return list(y_in_range)
[docs]def eig_plot(name, args):
fullpath = os.path.join(name, '.txt')
raw_data = []
started = 0
fid = open(fullpath)
for line in fid.readline():
if '#1' in line:
started = 1
elif 'PARTICIPATION FACTORS' in line:
started = -1
if started == 1:
raw_data.append(line)
elif started == -1:
break
fid.close()
for line in raw_data:
# data = line.split()
# TODO: complete this function
pass
[docs]def tdsplot(filename, y, x=(0,),
to_csv=False,
find=None,
xargs=None,
exclude=None,
**kwargs):
"""
TDS plot main function based on the new TDSData class.
Parameters
----------
filename : str
Path to the ANDES TDS output data file. Works without extension.
x : list or int, optional
The index for the x-axis variable. x=0 by default for time
y : list or int
The indices for the y-axis variable
to_csv : bool
True if need to export to a csv file
find : str, optional
if not none, specify the variable name to find
xargs : str, optional
similar to find, but return the result indices with file name, x idx name for xargs
exclude : str, optional
variable name pattern to exclude
Returns
-------
TDSData object
"""
# single data file
if len(filename) == 1:
tds_data = TDSData(filename[0])
if to_csv is True:
return tds_data.export_csv()
if find is not None:
out = tds_data.find(query=find, exclude=exclude)
print(out)
return out
if xargs is not None:
out = tds_data.find(query=xargs, exclude=exclude, idx_only=True)
out = [str(i) for i in out]
xargs_out = filename[0] + ' 0 ' + ' '.join(out)
print(xargs_out)
return xargs_out
if len(y) == 0:
logger.error('Must specify Y indices to plot.')
return tds_data
y_num = parse_y(y, lower=0, upper=tds_data.nvars)
tds_data.plot(xidx=x, yidx=y_num, **kwargs)
return tds_data
else:
raise NotImplementedError("Plotting multiple data files are not supported yet")
def _isint(value):
"""
Helper function to tell if ``value`` is convertable to an integer.
"""
try:
int(value)
return True
except ValueError:
return False
def _scale_func(k):
"""
Return a lambda function that scales its input by k
Parameters
----------
k : float
The scaling factor of the returned lambda function
Returns
-------
Lambda function
"""
return lambda y_values_input: k * y_values_input
def _label_latexify(label):
"""
Convert a label to latex format by appending surrounding $ and escaping spaces
Parameters
----------
label : str
The label string to be converted to latex expression
Returns
-------
str
A string with $ surrounding
"""
return '$' + label.replace(' ', r'\ ') + '$'