"""
plotting (:mod:`skrf.plotting`)
========================================
This module provides general plotting functions.
Plots and Charts
------------------
.. autosummary::
:toctree: generated/
smith
plot_smith
plot_rectangular
plot_polar
plot_complex_rectangular
plot_complex_polar
plot_v_frequency
plot_it_all
plot_minmax_bounds_component
plot_minmax_bounds_s_db
plot_minmax_bounds_s_db10
plot_minmax_bounds_s_time_db
plot_uncertainty_bounds_component
plot_uncertainty_bounds_s
plot_uncertainty_bounds_s_db
plot_uncertainty_bounds_s_time_db
plot_passivity
plot_logsigma
plot_circuit_graph
plot_contour
Convenience plotting functions
-------------------------------
.. autosummary::
:toctree: generated/
stylely
subplot_params
shade_bands
save_all_figs
scale_frequency_ticks
add_markers_to_lines
legend_off
func_on_all_figs
scrape_legend
signature
"""
from . constants import NumberLike
from numbers import Number
from typing import Callable, Tuple, Union, List
import os
import warnings
import numpy as npy
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import ticker, rcParams
from matplotlib.patches import Circle # for drawing smith chart
from matplotlib.pyplot import quiver
from matplotlib.dates import date2num
import matplotlib.tri as tri
from . import network, frequency, calibration, networkSet, circuit
from . import mathFunctions as mf
from . util import now_string_2_dt
try:
import networkx as nx
except ImportError as e:
pass
SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
SI_CONVERSION = {key: 10**((8-i)*3) for i, key in enumerate(SI_PREFIXES_ASCII)}
[docs]def scale_frequency_ticks(ax: plt.Axes, funit: str):
"""
Scale frequency axis ticks.
Parameters
----------
ax : plt.Axes
Matplotlib figure axe
funit : str
frequency unit string as in :data:`~skrf.frequency.Frequency.unit`
Raises
------
ValueError
if invalid unit is passed
"""
if funit.lower() == "hz":
prefix = " "
scale = 1
elif len(funit) == 3:
prefix = funit[0]
scale = SI_CONVERSION[prefix]
else:
raise ValueError(f"invalid funit {funit}")
ticks_x = ticker.FuncFormatter(lambda x, pos: f'{x * scale:g}')
ax.xaxis.set_major_formatter(ticks_x)
[docs]def smith(smithR: Number = 1, chart_type: str = 'z', draw_labels: bool = False,
border: bool = False, ax: Union[plt.Axes, None] = None, ref_imm: float = 1.0,
draw_vswr: Union[List, bool, None] = None):
"""
Plot the Smith chart of a given radius.
The Smith chart is used to assist in solving problems with transmission lines
and matching circuits. It can be used to simultaneously display multiple
parameters including impedances, admittances, reflection coefficients,
scattering parameters, noise figure circles, etc. [#]_
Parameters
----------
smithR : number, optional
radius of smith chart. Default is 1.
chart_type : str, optional
Contour type. Default is 'z'. Possible values are:
* *'z'* : lines of constant impedance
* *'y'* : lines of constant admittance
* *'zy'* : lines of constant impedance stronger than admittance
* *'yz'* : lines of constant admittance stronger than impedance
draw_labels : Boolean, optional
annotate real and imaginary parts of impedance on the
chart (only if smithR=1).
Default is False.
border : Boolean, optional.
draw a rectangular border with axis ticks, around the perimeter
of the figure. Not used if draw_labels = True.
Default is False.
ax : :class:`matplotlib.pyplot.Axes` or None, optional
existing axes to draw smith chart on.
Default is None (creates a new figure)
ref_imm : number, optional
Reference immittance for center of Smith chart. Only changes
labels, if printed.
Default is 1.0.
draw_vswr : list of numbers, Boolean or None, optional
draw VSWR circles. If True, default values are used.
Default is None.
References
----------
.. [#] https://en.wikipedia.org/wiki/Smith_chart
"""
##TODO: fix this function so it doesnt suck
if ax is None:
ax1 = plt.gca()
else:
ax1 = ax
# contour holds matplotlib instances of: pathes.Circle, and lines.Line2D, which
# are the contours on the smith chart
contour = []
# these are hard-coded on purpose,as they should always be present
rHeavyList = [0,1]
xHeavyList = [1,-1]
#TODO: fix this
# these could be dynamically coded in the future, but work good'nuff for now
if not draw_labels:
rLightList = npy.logspace(3,-5,9,base=.5)
xLightList = npy.hstack([npy.logspace(2,-5,8,base=.5), -1*npy.logspace(2,-5,8,base=.5)])
else:
rLightList = npy.array( [ 0.2, 0.5, 1.0, 2.0, 5.0 ] )
xLightList = npy.array( [ 0.2, 0.5, 1.0, 2.0 , 5.0, -0.2, -0.5, -1.0, -2.0, -5.0 ] )
# vswr lines
if isinstance(draw_vswr, (tuple,list)):
vswrVeryLightList = draw_vswr
elif draw_vswr is True:
# use the default I like
vswrVeryLightList = [1.5, 2.0, 3.0, 5.0]
else:
vswrVeryLightList = []
# cheap way to make a ok-looking smith chart at larger than 1 radii
if smithR > 1:
rMax = (1.+smithR)/(1.-smithR)
rLightList = npy.hstack([ npy.linspace(0,rMax,11) , rLightList ])
if chart_type.startswith('y'):
y_flip_sign = -1
else:
y_flip_sign = 1
# draw impedance and/or admittance
both_charts = chart_type in ('zy', 'yz')
# loops through Verylight, Light and Heavy lists and draws circles using patches
# for analysis of this see R.M. Weikles Microwave II notes (from uva)
superLightColor = dict(ec='whitesmoke', fc='none')
veryLightColor = dict(ec='lightgrey', fc='none')
lightColor = dict(ec='grey', fc='none')
heavyColor = dict(ec='black', fc='none')
# vswr circles verylight
for vswr in vswrVeryLightList:
radius = (vswr-1.0) / (vswr+1.0)
contour.append( Circle((0, 0), radius, **veryLightColor))
# impedance/admittance circles
for r in rLightList:
center = (r/(1.+r)*y_flip_sign,0 )
radius = 1./(1+r)
if both_charts:
contour.insert(0, Circle((-center[0], center[1]), radius, **superLightColor))
contour.append(Circle(center, radius, **lightColor))
for x in xLightList:
center = (1*y_flip_sign,1./x)
radius = 1./x
if both_charts:
contour.insert(0, Circle( (-center[0], center[1]), radius, **superLightColor))
contour.append(Circle(center, radius, **lightColor))
for r in rHeavyList:
center = (r/(1.+r)*y_flip_sign,0 )
radius = 1./(1+r)
contour.append(Circle(center, radius, **heavyColor))
for x in xHeavyList:
center = (1*y_flip_sign,1./x)
radius = 1./x
contour.append(Circle(center, radius, **heavyColor))
# clipping circle
clipc = Circle( [0,0], smithR, ec='k',fc='None',visible=True)
ax1.add_patch( clipc)
#draw x and y axis
ax1.axhline(0, color='k', lw=.1, clip_path=clipc)
ax1.axvline(1*y_flip_sign, color='k', clip_path=clipc)
ax1.grid(0)
# Set axis limits by plotting white points so zooming works properly
ax1.plot(smithR*npy.array([-1.1, 1.1]), smithR*npy.array([-1.1, 1.1]), 'w.', markersize = 0)
ax1.axis('image') # Combination of 'equal' and 'tight'
if not border:
ax1.yaxis.set_ticks([])
ax1.xaxis.set_ticks([])
for loc, spine in ax1.spines.items():
spine.set_color('none')
if draw_labels:
#Clear axis
ax1.yaxis.set_ticks([])
ax1.xaxis.set_ticks([])
for loc, spine in ax1.spines.items():
spine.set_color('none')
# Make annotations only if the radius is 1
if smithR == 1:
#Make room for annotation
ax1.plot(npy.array([-1.25, 1.25]), npy.array([-1.1, 1.1]), 'w.', markersize = 0)
ax1.axis('image')
#Annotate real part
for value in rLightList:
# Set radius of real part's label; offset slightly left (Z
# chart, y_flip_sign == 1) or right (Y chart, y_flip_sign == -1)
# so label doesn't overlap chart's circles
rho = (value - 1)/(value + 1) - y_flip_sign*0.01
if y_flip_sign == 1:
halignstyle = "right"
else:
halignstyle = "left"
if y_flip_sign == -1: # 'y' and 'yz' charts
value = 1/value
ax1.annotate(str(value*ref_imm), xy=(rho*smithR, 0.01),
xytext=(rho*smithR, 0.01), ha = halignstyle, va = "baseline")
#Annotate imaginary part
radialScaleFactor = 1.01 # Scale radius of label position by this
# factor. Making it >1 places the label
# outside the Smith chart's circle
for value in xLightList:
#Transforms from complex to cartesian
S = (1j*value - 1) / (1j*value + 1)
S *= smithR * radialScaleFactor
rhox = S.real
rhoy = S.imag * y_flip_sign
# Choose alignment anchor point based on label's value
if ((value == 1.0) or (value == -1.0)):
halignstyle = "center"
elif (rhox < 0.0):
halignstyle = "right"
else:
halignstyle = "left"
if (rhoy < 0):
valignstyle = "top"
else:
valignstyle = "bottom"
if y_flip_sign == -1: # 'y' and 'yz' charts
value = 1/value
#Annotate value
ax1.annotate(str(value*ref_imm) + 'j', xy=(rhox, rhoy),
xytext=(rhox, rhoy), ha = halignstyle, va = valignstyle)
#Annotate 0 and inf
if y_flip_sign == 1: # z and zy charts
label_left, label_right = '0.0', r'$\infty$'
else: # y and yz charts
label_left, label_right = r'$\infty$', '0.0'
ax1.annotate(label_left, xy=(-1.02, 0), xytext=(-1.02, 0),
ha = "right", va = "center")
ax1.annotate(label_right, xy=(radialScaleFactor, 0), xytext=(radialScaleFactor, 0),
ha = "left", va = "center")
# annotate vswr circles
for vswr in vswrVeryLightList:
rhoy = (vswr-1.0) / (vswr+1.0)
ax1.annotate(str(vswr), xy=(0, rhoy*smithR),
xytext=(0, rhoy*smithR), ha="center", va="bottom",
color='grey', size='smaller')
# loop though contours and draw them on the given axes
for currentContour in contour:
cc=ax1.add_patch(currentContour)
cc.set_clip_path(clipc)
[docs]def plot_rectangular(x: NumberLike, y: NumberLike,
x_label: Union[str, None] = None, y_label: Union[str, None] = None,
title: Union[str, None] = None, show_legend: bool = True,
axis: str = 'tight', ax: Union[plt.Axes, None] = None,
*args, **kwargs):
r"""
Plot rectangular data and optionally label axes.
Parameters
----------
x : array-like, of complex data
data to plot
y : array-like, of complex data
data to plot
x_label : string or None, optional.
x-axis label. Default is None.
y_label : string or None, optional.
y-axis label. Default is None.
title : string or None, optional.
plot title. Default is None.
show_legend : Boolean, optional.
controls the drawing of the legend. Default is True.
axis : str, optional
whether or not to autoscale the axis. Default is 'tight'
ax : :class:`matplotlib.axes.AxesSubplot` object or None, optional.
axes to draw on. Default is None (creates a new figure)
\*args, \*\*kwargs : passed to pylab.plot
"""
if ax is None:
ax = plt.gca()
my_plot = ax.plot(x, y, *args, **kwargs)
if x_label is not None:
ax.set_xlabel(x_label)
if y_label is not None:
ax.set_ylabel(y_label)
if title is not None:
ax.set_title(title)
if show_legend:
# only show legend if they provide a label
if 'label' in kwargs:
ax.legend()
if axis is not None:
ax.autoscale(True, 'x', True)
ax.autoscale(True, 'y', False)
if plt.isinteractive():
plt.draw()
return my_plot
[docs]def plot_polar(theta: NumberLike, r: NumberLike,
x_label: Union[str, None] = None, y_label: Union[str, None] = None,
title: Union[str, None] = None, show_legend: bool = True,
axis_equal: bool = False, ax: Union[plt.Axes, None] = None,
*args, **kwargs):
r"""
Plot polar data on a polar plot and optionally label axes.
Parameters
----------
theta : array-like
angular data to plot
r : array-like
radial data to plot
x_label : string or None, optional
x-axis label. Default is None.
y_label : string or None, optional.
y-axis label. Default is None
title : string or None, optional.
plot title. Default is None.
show_legend : Boolean, optional.
controls the drawing of the legend. Default is True.
ax : :class:`matplotlib.axes.AxesSubplot` object or None.
axes to draw on. Default is None (creates a new figure).
\*args, \*\*kwargs : passed to pylab.plot
See Also
--------
plot_rectangular : plots rectangular data
plot_complex_rectangular : plot complex data on complex plane
plot_polar : plot polar data
plot_complex_polar : plot complex data on polar plane
plot_smith : plot complex data on smith chart
"""
if ax is None:
# no Axes passed
# if an existing (polar) plot is already present, grab and use its Axes
# otherwise, create a new polar plot and use that Axes
if not plt.get_fignums() or not plt.gcf().axes or plt.gca().name != 'polar':
ax = plt.figure().add_subplot(projection='polar')
else:
ax = plt.gca()
else:
if ax.name != 'polar':
# The projection of an existing axes can't be changed,
# since specifying a projection when creating an axes determines the
# axes class you get, which is different for each projection type.
# So, passing a axe projection not polar is probably undesired
warnings.warn(
f"Projection of the Axes passed as `ax` is not 'polar' but is {ax.name}." +
"See Matplotlib documentation to create a polar plot or call this function without the `ax` parameter."
)
ax.plot(theta, r, *args, **kwargs)
if x_label is not None:
ax.set_xlabel(x_label)
if y_label is not None:
ax.set_ylabel(y_label)
if title is not None:
ax.set_title(title)
if show_legend:
# only show legend if they provide a label
if 'label' in kwargs:
ax.legend()
if axis_equal:
ax.axis('equal')
if plt.isinteractive():
plt.draw()
[docs]def plot_complex_rectangular(z: NumberLike,
x_label: str = 'Real', y_label: str = 'Imag',
title: str = 'Complex Plane', show_legend: bool = True,
axis: str = 'equal', ax: Union[plt.Axes, None] = None,
*args, **kwargs):
r"""
Plot complex data on the complex plane.
Parameters
----------
z : array-like, of complex data
data to plot
x_label : string, optional.
x-axis label. Default is 'Real'.
y_label : string, optional.
y-axis label. Default is 'Imag'.
title : string, optional.
plot title. Default is 'Complex Plane'
show_legend : Boolean, optional.
controls the drawing of the legend. Default is True.
ax : :class:`matplotlib.axes.AxesSubplot` object or None.
axes to draw on. Default is None (creates a new figure)
\*args, \*\*kwargs : passed to pylab.plot
See Also
--------
plot_rectangular : plots rectangular data
plot_complex_rectangular : plot complex data on complex plane
plot_polar : plot polar data
plot_complex_polar : plot complex data on polar plane
plot_smith : plot complex data on smith chart
"""
x = npy.real(z)
y = npy.imag(z)
plot_rectangular(x=x, y=y, x_label=x_label, y_label=y_label,
title=title, show_legend=show_legend, axis=axis,
ax=ax, *args, **kwargs)
[docs]def plot_complex_polar(z: NumberLike,
x_label: Union[str, None] = None, y_label: Union[str, None] = None,
title: Union[str, None] = None, show_legend: bool = True,
axis_equal: bool = False, ax: Union[plt.Axes, None] = None,
*args, **kwargs):
r"""
Plot complex data in polar format.
Parameters
----------
z : array-like, of complex data
data to plot
x_label : string or None, optional
x-axis label. Default is None.
y_label : string or None, optional.
y-axis label. Default is None
title : string or None, optional.
plot title. Default is None.
show_legend : Boolean, optional.
controls the drawing of the legend. Default is True.
ax : :class:`matplotlib.axes.AxesSubplot` object or None.
axes to draw on. Default is None (creates a new figure).
\*args, \*\*kwargs : passed to pylab.plot
See Also
--------
plot_rectangular : plots rectangular data
plot_complex_rectangular : plot complex data on complex plane
plot_polar : plot polar data
plot_complex_polar : plot complex data on polar plane
plot_smith : plot complex data on smith chart
"""
theta = npy.angle(z)
r = npy.abs(z)
plot_polar(theta=theta, r=r, x_label=x_label, y_label=y_label,
title=title, show_legend=show_legend, axis_equal=axis_equal,
ax=ax, *args, **kwargs)
[docs]def plot_smith(s: NumberLike, smith_r: float = 1, chart_type: str = 'z',
x_label: str = 'Real', y_label: str = 'Imaginary', title: str = 'Complex Plane',
show_legend: bool = True, axis: str = 'equal', ax: Union[plt.Axes, None] = None,
force_chart: bool = False, draw_vswr: Union[List, bool, None] = None, draw_labels: bool = False,
*args, **kwargs):
r"""
Plot complex data on smith chart.
Parameters
------------
s : complex array-like
reflection-coefficient-like data to plot
smith_r : number
radius of smith chart
chart_type : str in ['z','y']
Contour type for chart.
* *'z'* : lines of constant impedance
* *'y'* : lines of constant admittance
x_label : string, optional.
x-axis label. Default is 'Real'.
y_label : string, optional.
y-axis label. Default is 'Imaginary'
title : string, optional.
plot title, Default is 'Complex Plane'.
show_legend : Boolean, optional.
controls the drawing of the legend. Default is True.
axis_equal: Boolean, optional.
sets axis to be equal increments. Default is 'equal'.
ax : :class:`matplotlib.axes.AxesSubplot` object or None.
axes to draw on. Default is None (creates a new figure).
force_chart : Boolean, optional.
forces the re-drawing of smith chart. Default is False.
draw_vswr : list of numbers, Boolean or None, optional
draw VSWR circles. If True, default values are used.
Default is None.
draw_labels : Boolean
annotate chart with impedance values
\*args, \*\*kwargs : passed to pylab.plot
See Also
----------
plot_rectangular : plots rectangular data
plot_complex_rectangular : plot complex data on complex plane
plot_polar : plot polar data
plot_complex_polar : plot complex data on polar plane
plot_smith : plot complex data on smith chart
"""
if ax is None:
ax = plt.gca()
# test if smith chart is already drawn
if not force_chart:
if len(ax.patches) == 0:
smith(ax=ax, smithR = smith_r, chart_type=chart_type, draw_vswr=draw_vswr, draw_labels=draw_labels)
plot_complex_rectangular(s, x_label=x_label, y_label=y_label,
title=title, show_legend=show_legend, axis=axis,
ax=ax, *args, **kwargs)
ax.axis(smith_r*npy.array([-1.1, 1.1, -1.1, 1.1]))
if plt.isinteractive():
plt.draw()
[docs]def subplot_params(ntwk, param: str = 's', proj: str = 'db',
size_per_port: int = 4, newfig: bool = True,
add_titles: bool = True, keep_it_tight: bool = True,
subplot_kw: dict = {},
*args, **kw):
"""
Plot all networks parameters individually on subplots.
Parameters
----------
ntwk : :class:`~skrf.network.Network`
Network to get data from.
param : str, optional
Parameter to plot, by default 's'
proj : str, optional
Projection type, by default 'db'
size_per_port : int, optional
by default 4
newfig : bool, optional
by default True
add_titles : bool, optional
by default True
keep_it_tight : bool, optional
by default True
subplot_kw : dict, optional
by default {}
Returns
-------
f : :class:`matplotlib.pyplot.Figure`
Matplotlib Figure
ax : :class:`matplotlib.pyplot.Axes`
Matplotlib Axes
"""
if newfig:
f,axs= plt.subplots(ntwk.nports,ntwk.nports,
figsize =(size_per_port*ntwk.nports,
size_per_port*ntwk.nports ),
**subplot_kw)
else:
f = plt.gcf()
axs = npy.array(f.get_axes())
for ports,ax in zip(ntwk.port_tuples, axs.flatten()):
plot_func = ntwk.__getattribute__('plot_%s_%s'%(param, proj))
plot_func(m=ports[0], n=ports[1], ax=ax,*args, **kw)
if add_titles:
ax.set_title('%s%i%i'%(param.upper(),ports[0]+1, ports[1]+1))
if keep_it_tight:
plt.tight_layout()
return f, axs
[docs]def shade_bands(edges: NumberLike, y_range: Union[Tuple, None] = None,
cmap: str = 'prism', **kwargs):
r"""
Shades frequency bands.
When plotting data over a set of frequency bands it is nice to
have each band visually separated from the other. The kwarg `alpha`
is useful.
Parameters
----------
edges : array-like
x-values separating regions of a given shade
y_range : tuple or None, optional.
y-values to shade in. Default is None.
cmap : str, optional.
see matplotlib.cm or matplotlib.colormaps for acceptable values.
Default is 'prism'.
\*\*kwargs : key word arguments
passed to `matplotlib.fill_between`
Examples
--------
>>> rf.shade_bands([325,500,750,1100], alpha=.2)
"""
cmap = plt.cm.get_cmap(cmap)
if not isinstance(y_range, (tuple, list)) or (len(y_range) != 2):
y_range=plt.gca().get_ylim()
axis = plt.axis()
for k in range(len(edges)-1):
plt.fill_between(
[edges[k],edges[k+1]],
y_range[0], y_range[1],
color = cmap(1.0*k/len(edges)),
**kwargs)
plt.axis(axis)
[docs]def save_all_figs(dir: str = './', format: Union[None, List[str]] = None,
replace_spaces: bool = True, echo: bool = True):
"""
Save all open Figures to disk.
Parameters
----------
dir : string, optional.
path to save figures into. Default is './'
format : None or list of strings, optional.
the types of formats to save figures as. The elements of this
list are passed to :func:`matplotlib.pyplot.savefig`. This is a list so that
you can save each figure in multiple formats. Default is None.
replace_spaces : bool, optional
default is True.
echo : bool, optional.
True prints filenames as they are saved. Default is True.
"""
if dir[-1] != '/':
dir = dir + '/'
for fignum in plt.get_fignums():
fileName = plt.figure(fignum).get_axes()[0].get_title()
if replace_spaces:
fileName = fileName.replace(' ','_')
if fileName == '':
fileName = 'unnamedPlot'
if format is None:
plt.savefig(dir+fileName)
if echo:
print(dir+fileName)
else:
for fmt in format:
plt.savefig(dir+fileName+'.'+fmt, format=fmt)
if echo:
print(dir+fileName+'.'+fmt)
saf = save_all_figs
[docs]def add_markers_to_lines(ax: Union[plt.Axes, None] = None,
marker_list: List = ['o', 'D', 's', '+', 'x'],
markevery: int = 10):
"""
Add markers to existing lings on a plot.
Convenient if you have already have a plot made, but then
need to add markers afterwards, so that it can be interpreted in
black and white. The markevery argument makes the markers less
frequent than the data, which is generally what you want.
Parameters
----------
ax : matplotlib.Axes or None, optional
axis which to add markers to.
Default is current axe gca()
marker_list : list of string, optional
list of marker characters. Default is ['o', 'D', 's', '+', 'x'].
see matplotlib.plot help for possible marker characters
markevery : int, optional.
markevery number of points with a marker.
Default is 10.
"""
if ax is None:
ax=plt.gca()
lines = ax.get_lines()
if len(lines) > len (marker_list ):
marker_list *= 3
[k[0].set_marker(k[1]) for k in zip(lines, marker_list)]
[line.set_markevery(markevery) for line in lines]
[docs]def legend_off(ax: Union[plt.Axes, None] = None):
"""
Turn off the legend for a given axes.
If no axes is given then it will use current axes.
Parameters
----------
ax : matplotlib.Axes or None, optional
axis to operate on.
Default is None for current axe gca()
"""
if ax is None:
plt.gca().legend_.set_visible(0)
else:
ax.legend_.set_visible(0)
[docs]def scrape_legend(n: Union[int, None] = None,
ax: Union[plt.Axes, None] = None):
"""
Scrape a legend with redundant labels.
Given a legend of m entries of n groups, this will remove all but
every m/nth entry. This is used when you plot many lines representing
the same thing, and only want one label entry in the legend for the
whole ensemble of lines.
Parameters
----------
n : int or None, optional.
Default is None.
ax : matplotlib.Axes or None, optional
axis to operate on.
Default is None for current axe gca()
"""
if ax is None:
ax = plt.gca()
handles, labels = ax.get_legend_handles_labels()
if n is None:
n =len ( set(labels))
if n>len(handles):
raise ValueError('number of entries is too large')
k_list = [int(k) for k in npy.linspace(0,len(handles)-1,n)]
ax.legend([handles[k] for k in k_list], [labels[k] for k in k_list])
[docs]def func_on_all_figs(func: Callable, *args, **kwargs):
r"""
Run a function after making all open figures current.
Useful if you need to change the properties of many open figures
at once, like turn off the grid.
Parameters
----------
func : function
function to call
\*args, \*\*kwargs : passed to func
Examples
--------
>>> rf.func_on_all_figs(grid, alpha=.3)
"""
for fig_n in plt.get_fignums():
fig = plt.figure(fig_n)
for ax_n in fig.axes:
fig.add_axes(ax_n) # trick to make axes current
func(*args, **kwargs)
plt.draw()
foaf = func_on_all_figs
def plot_vector(a: complex, off: complex = 0+0j, *args, **kwargs):
"""
Plot a 2d vector.
Parameters
----------
a : complex
complex coordinates (real for X, imag for Y) of the arrow location.
off : complex, optional
complex direction (real for U, imag for V) components
of the arrow vectors, by default 0+0j
Returns
-------
quiver : matplotlib.pyplot.quiver
"""
return quiver(off.real, off.imag, a.real, a.imag, scale_units='xy',
angles='xy', scale=1, *args, **kwargs)
def colors() -> List[str]:
"""
Return the list of colors of the rcParams color cycle.
Returns
-------
colors : List[str]
"""
return [c['color'] for c in rcParams['axes.prop_cycle']]
PRIMARY_PROPERTIES = network.PRIMARY_PROPERTIES
COMPONENT_FUNC_DICT = network.COMPONENT_FUNC_DICT
Y_LABEL_DICT = network.Y_LABEL_DICT
# TODO: remove this as it takes up ~70% cpu time of this init
def setup_matplotlib_plotting():
frequency.Frequency.labelXAxis = labelXAxis
frequency.Frequency.plot = plot_v_frequency
__generate_plot_functions(network.Network)
network.Network.plot = plot
network.Network.plot_passivity = plot_passivity
network.Network.plot_reciprocity = plot_reciprocity
network.Network.plot_reciprocity2 = plot_reciprocity2
network.Network.plot_s_db_time = plot_s_db_time
network.Network.plot_s_smith = plot_s_smith
network.Network.plot_it_all = plot_it_all
calibration.Calibration.plot_errors = plot_calibration_errors
calibration.Calibration.plot_caled_ntwks = plot_caled_ntwks
calibration.Calibration.plot_residuals = plot_residuals
networkSet.NetworkSet.animate = animate
networkSet.NetworkSet.plot_uncertainty_bounds_component = plot_uncertainty_bounds_component
networkSet.NetworkSet.plot_minmax_bounds_component = plot_minmax_bounds_component
networkSet.NetworkSet.plot_uncertainty_bounds_s_db = plot_uncertainty_bounds_s_db
networkSet.NetworkSet.plot_minmax_bounds_s_db = plot_minmax_bounds_s_db
networkSet.NetworkSet.plot_minmax_bounds_s_db10 = plot_minmax_bounds_s_db10
networkSet.NetworkSet.plot_uncertainty_bounds_s_time_db = plot_uncertainty_bounds_s_time_db
networkSet.NetworkSet.plot_minmax_bounds_s_time_db = plot_minmax_bounds_s_time_db
networkSet.NetworkSet.plot_uncertainty_decomposition = plot_uncertainty_decomposition
networkSet.NetworkSet.plot_uncertainty_bounds_s = plot_uncertainty_bounds_s
networkSet.NetworkSet.plot_logsigma = plot_logsigma
networkSet.NetworkSet.signature = signature
circuit.Circuit.plot_graph = plot_circuit_graph
def __generate_plot_functions(self):
"""
"""
for prop_name in PRIMARY_PROPERTIES:
def plot_prop_polar(self,
m=None, n=None, ax=None,
show_legend=True, prop_name=prop_name, *args, **kwargs):
# create index lists, if not provided by user
if m is None:
M = range(self.number_of_ports)
else:
M = [m]
if n is None:
N = range(self.number_of_ports)
else:
N = [n]
if 'label' not in kwargs.keys():
gen_label = True
else:
gen_label = False
# was_interactive = plt.isinteractive
# if was_interactive:
# plt.interactive(False)
for m in M:
for n in N:
# set the legend label for this trace to the networks
# name if it exists, and they didn't pass a name key in
# the kwargs
if gen_label:
if self.name is None:
if plt.rcParams['text.usetex']:
label_string = '$%s_{%i%i}$'%\
(prop_name[0].upper(),m+1,n+1)
else:
label_string = '%s%i%i'%\
(prop_name[0].upper(),m+1,n+1)
else:
if plt.rcParams['text.usetex']:
label_string = self.name+', $%s_{%i%i}$'%\
(prop_name[0].upper(),m+1,n+1)
else:
label_string = self.name+', %s%i%i'%\
(prop_name[0].upper(),m+1,n+1)
kwargs['label'] = label_string
# plot the desired attribute vs frequency
plot_complex_polar(
z = getattr(self,prop_name)[:,m,n],
show_legend = show_legend, ax = ax,
*args, **kwargs)
# if was_interactive:
# plt.interactive(True)
# plt.draw()
# plt.show()
plot_prop_polar.__doc__ = r"""
Plot the Network attribute :attr:`{}` vs frequency.
Parameters
----------
m : int, optional
first index of s-parameter matrix, if None will use all
n : int, optional
second index of the s-parameter matrix, if None will use all
ax : :class:`matplotlib.Axes` object, optional
An existing Axes object to plot on
show_legend : Boolean
draw legend or not
attribute : string
Network attribute to plot
y_label : string, optional
the y-axis label
\*args,\**kwargs : arguments, keyword arguments
passed to :func:`matplotlib.plot`
Note
----
This function is dynamically generated upon Network
initialization. This is accomplished by calling
:func:`plot_vs_frequency_generic`
Examples
--------
>>> myntwk.plot_{}(m=1,n=0,color='r')
""".format(prop_name, prop_name)
# setattr(self.__class__,'plot_%s_polar'%(prop_name), \
setattr(self, 'plot_%s_polar'%(prop_name), plot_prop_polar)
def plot_prop_rect(self,
m=None, n=None, ax=None,
show_legend=True, prop_name=prop_name, *args, **kwargs):
# create index lists, if not provided by user
if m is None:
M = range(self.number_of_ports)
else:
M = [m]
if n is None:
N = range(self.number_of_ports)
else:
N = [n]
if 'label' not in kwargs.keys():
gen_label = True
else:
gen_label = False
#was_interactive = plt.isinteractive
#if was_interactive:
# plt.interactive(False)
for m in M:
for n in N:
# set the legend label for this trace to the networks
# name if it exists, and they didn't pass a name key in
# the kwargs
if gen_label:
if self.name is None:
if plt.rcParams['text.usetex']:
label_string = '$%s_{%i%i}$'%\
(prop_name[0].upper(),m+1,n+1)
else:
label_string = '%s%i%i'%\
(prop_name[0].upper(),m+1,n+1)
else:
if plt.rcParams['text.usetex']:
label_string = self.name+', $%s_{%i%i}$'%\
(prop_name[0].upper(),m+1,n+1)
else:
label_string = self.name+', %s%i%i'%\
(prop_name[0].upper(),m+1,n+1)
kwargs['label'] = label_string
# plot the desired attribute vs frequency
plot_complex_rectangular(
z=getattr(self, prop_name)[:, m, n],
show_legend=show_legend, ax=ax,
*args, **kwargs)
#if was_interactive:
# plt.interactive(True)
# plt.draw()
# plt.show()
plot_prop_rect.__doc__ = r"""
Plot the Network attribute :attr:`{}` vs frequency.
Parameters
----------
m : int, optional
first index of s-parameter matrix, if None will use all
n : int, optional
second index of the s-parameter matrix, if None will use all
ax : :class:`matplotlib.Axes` object, optional
An existing Axes object to plot on
show_legend : Boolean
draw legend or not
attribute : string
Network attribute to plot
y_label : string, optional
the y-axis label
\*args,\**kwargs : arguments, keyword arguments
passed to :func:`matplotlib.plot`
Note
----
This function is dynamically generated upon Network
initialization. This is accomplished by calling
:func:`plot_vs_frequency_generic`
Examples
--------
>>> myntwk.plot_{}(m=1,n=0,color='r')
""".format(prop_name, prop_name)
# setattr(self.__class__,'plot_%s_complex'%(prop_name), \
setattr(self,'plot_%s_complex'%(prop_name), \
plot_prop_rect)
for func_name in COMPONENT_FUNC_DICT:
attribute = f'{prop_name}_{func_name}'
y_label = Y_LABEL_DICT[func_name]
def plot_func(self, m=None, n=None, ax=None,
show_legend=True, attribute=attribute,
y_label=y_label, logx=False, pad=0, window='hamming', z0=50, *args, **kwargs):
# create index lists, if not provided by user
if m is None:
M = range(self.number_of_ports)
else:
M = [m]
if n is None:
N = range(self.number_of_ports)
else:
N = [n]
if 'label' not in kwargs.keys():
gen_label = True
else:
gen_label = False
#TODO: turn off interactive plotting for performance
# this didnt work because it required a show()
# to be called, which in turn, disrupted testCases
#
# was_interactive = plt.isinteractive
# if was_interactive:
# plt.interactive(False)
for m in M:
for n in N:
# set the legend label for this trace to the networks
# name if it exists, and they didn't pass a name key in
# the kwargs
if gen_label:
if self.name is None:
if plt.rcParams['text.usetex']:
label_string = '$%s_{%i%i}$'%\
(attribute[0].upper(),m+1,n+1)
else:
label_string = '%s%i%i'%\
(attribute[0].upper(),m+1,n+1)
else:
if plt.rcParams['text.usetex']:
label_string = self.name+', $%s_{%i%i}$'%\
(attribute[0].upper(),m+1,n+1)
else:
label_string = self.name+', %s%i%i'%\
(attribute[0].upper(),m+1,n+1)
kwargs['label'] = label_string
# quick and dirty way to plot step and impulse response
if 'time_impulse' in attribute:
xlabel = 'Time (ns)'
x,y = self.impulse_response(pad=pad, window=window)
# default is reflexion coefficient axis
if attribute[0].lower() == 'z':
# if they want impedance axis, give it to them
y_label = 'Z (ohm)'
y[x == 1.] = 1. + 1e-12 # solve numerical singularity
y[x == -1.] = -1. + 1e-12 # solve numerical singularity
y = z0 * (1+y) / (1-y)
plot_rectangular(x=x * 1e9,
y=y,
x_label=xlabel,
y_label=y_label,
show_legend=show_legend, ax=ax,
*args, **kwargs)
elif 'time_step' in attribute:
xlabel = 'Time (ns)'
x, y = self.step_response(pad=pad, window=window)
# default is reflexion coefficient axis
if attribute[0].lower() == 'z':
# if they want impedance axis, give it to them
y_label = 'Z (ohm)'
y[x == 1.] = 1. + 1e-12 # solve numerical singularity
y[x == -1.] = -1. + 1e-12 # solve numerical singularity
y = z0 * (1+y) / (1-y)
plot_rectangular(x=x * 1e9,
y=y,
x_label=xlabel,
y_label=y_label,
show_legend=show_legend, ax=ax,
*args, **kwargs)
else:
# plot the desired attribute vs frequency
if 'time' in attribute:
xlabel = 'Time (ns)'
x = self.frequency.t_ns
else:
xlabel = 'Frequency (%s)' % self.frequency.unit
# x = self.frequency.f_scaled
x = self.frequency.f # always plot f, and then scale the ticks instead
# scale the ticklabels according to the frequency unit and set log-scale if desired:
if ax is None:
ax = plt.gca()
if logx:
ax.set_xscale('log')
scale_frequency_ticks(ax, self.frequency.unit)
plot_rectangular(x=x,
y=getattr(self, attribute)[:, m, n],
x_label=xlabel,
y_label=y_label,
show_legend=show_legend, ax=ax,
*args, **kwargs)
#if was_interactive:
# plt.interactive(True)
# plt.draw()
# #plt.show()
plot_func.__doc__ = r"""
Plot the Network attribute :attr:`%s` vs frequency.
Parameters
----------
m : int, optional
first index of s-parameter matrix, if None will use all
n : int, optional
second index of the s-parameter matrix, if None will use all
ax : :class:`matplotlib.Axes` object, optional
An existing Axes object to plot on
show_legend : Boolean
draw legend or not
attribute : string
Network attribute to plot
y_label : string, optional
the y-axis label
logx : Boolean, optional
Enable logarithmic x-axis, default off
\*args,\**kwargs : arguments, keyword arguments
passed to :func:`matplotlib.plot`
Note
----
This function is dynamically generated upon Network
initialization. This is accomplished by calling
:func:`plot_vs_frequency_generic`
Examples
--------
>>> myntwk.plot_%s(m=1,n=0,color='r')
"""%(attribute,attribute)
# setattr(self.__class__,'plot_%s'%(attribute), \
setattr(self,'plot_%s'%(attribute), \
plot_func)
def labelXAxis(self, ax: Union[plt.Axes, None] = None):
"""
Label the x-axis of a plot.
Sets the labels of a plot using :func:`matplotlib.x_label` with
string containing the frequency unit.
Parameters
----------
ax : :class:`matplotlib.Axes` or None, optional
Axes on which to label the plot.
Defaults is None, for the current axe
returned by :func:`matplotlib.gca()`
"""
if ax is None:
ax = plt.gca()
ax.set_xlabel('Frequency (%s)' % self.unit)
[docs]def plot_v_frequency(self, y: NumberLike, *args, **kwargs):
"""
Plot something vs this frequency.
This plots whatever is given vs. `self.f_scaled` and then
calls `labelXAxis`.
"""
try:
if len(npy.shape(y)) > 2:
# perhaps the dimensions are empty, try to squeeze it down
y = y.squeeze()
if len(npy.shape(y)) > 2:
# the dimensions are full, so lets loop and plot each
for m in range(npy.shape(y)[1]):
for n in range(npy.shape(y)[2]):
self.plot(y[:, m, n], *args, **kwargs)
return
if len(y) == len(self):
pass
else:
raise IndexError(['thing to plot doesn\'t have same'
' number of points as f'])
except(TypeError):
y = y * npy.ones(len(self))
# plt.plot(self.f_scaled, y, *args, **kwargs)
plt.plot(self.f, y, *args, **kwargs)
ax = plt.gca()
scale_frequency_ticks(ax, self.unit)
plt.autoscale(axis='x', tight=True)
self.labelXAxis()
## specific plotting functions
def plot(self, *args, **kw):
"""
Plot something vs frequency
"""
return self.frequency.plot(*args, **kw)
[docs]def plot_passivity(self, port=None, label_prefix=None, *args, **kwargs):
"""
Plot dB(diag(passivity metric)) vs frequency.
Note
----
This plot does not completely capture the passivity metric, which
is a test for `unitary-ness` of the s-matrix. However, it may
be used to display a measure of power dissipated in a network.
See Also
--------
passivity
"""
name = '' if self.name is None else self.name
if port is None:
ports = range(self.nports)
else:
ports = [port]
for k in ports:
if label_prefix is None:
label = name + ', port %i' % (k + 1)
else:
label = label_prefix + ', port %i' % (k + 1)
self.frequency.plot(mf.complex_2_db(self.passivity[:, k, k]),
label=label,
*args, **kwargs)
plt.legend()
plt.draw()
def plot_reciprocity(self, db=False, *args, **kwargs):
"""
Plot reciprocity metric.
See Also
--------
reciprocity
"""
for m in range(self.nports):
for n in range(self.nports):
if m > n:
if 'label' not in kwargs.keys():
kwargs['label'] = 'ports %i%i' % (m, n)
y = self.reciprocity[:, m, n].flatten()
if db:
y = mf.complex_2_db(y)
self.frequency.plot(y, *args, **kwargs)
plt.legend()
plt.draw()
def plot_reciprocity2(self, db=False, *args, **kwargs):
"""
Plot reciprocity metric #2.
This is distance of the determinant of the wave-cascading matrix
from unity.
.. math::
abs(1 - S/S^T )
See Also
--------
reciprocity
"""
for m in range(self.nports):
for n in range(self.nports):
if m > n:
if 'label' not in kwargs.keys():
kwargs['label'] = 'ports %i%i' % (m, n)
y = self.reciprocity2[:, m, n].flatten()
if db:
y = mf.complex_2_db(y)
self.frequency.plot(y, *args, **kwargs)
plt.legend()
plt.draw()
def plot_s_db_time(self, *args, window: Union[str, float, Tuple[str, float]]=('kaiser', 6),
normalize: bool = True, center_to_dc: bool = None, **kwargs):
return self.windowed(window, normalize, center_to_dc).plot_s_time_db(*args,**kwargs)
# plotting
def plot_s_smith(self, m=None, n=None,r=1, ax=None, show_legend=True,\
chart_type='z', draw_labels=False, label_axes=False, draw_vswr=None, *args,**kwargs):
r"""
Plots the scattering parameter on a smith chart.
Plots indices `m`, `n`, where `m` and `n` can be integers or
lists of integers.
Parameters
----------
m : int, optional
first index
n : int, optional
second index
ax : matplotlib.Axes object, optional
axes to plot on. in case you want to update an existing
plot.
show_legend : boolean, optional
to turn legend show legend of not, optional
chart_type : ['z','y']
draw impedance or admittance contours
draw_labels : Boolean
annotate chart with impedance values
label_axes : Boolean
Label axis with titles `Real` and `Imaginary`
border : Boolean
draw rectangular border around image with ticks
draw_vswr : list of numbers, Boolean or None
draw VSWR circles. If True, default values are used.
\*args : arguments, optional
passed to the matplotlib.plot command
\*\*kwargs : keyword arguments, optional
passed to the matplotlib.plot command
See Also
--------
plot_vs_frequency_generic - generic plotting function
smith - draws a smith chart
Examples
--------
>>> myntwk.plot_s_smith()
>>> myntwk.plot_s_smith(m=0,n=1,color='b', marker='x')
"""
# TODO: prevent this from re-drawing smith chart if one alread
# exists on current set of axes
# get current axis if user doesnt supply and axis
if ax is None:
ax = plt.gca()
if m is None:
M = range(self.number_of_ports)
else:
M = [m]
if n is None:
N = range(self.number_of_ports)
else:
N = [n]
if 'label' not in kwargs.keys():
generate_label=True
else:
generate_label=False
for m in M:
for n in N:
# set the legend label for this trace to the networks name if it
# exists, and they didnt pass a name key in the kwargs
if generate_label:
if self.name is None:
if plt.rcParams['text.usetex']:
label_string = '$S_{'+repr(m+1) + repr(n+1)+'}$'
else:
label_string = 'S'+repr(m+1) + repr(n+1)
else:
if plt.rcParams['text.usetex']:
label_string = self.name+', $S_{'+repr(m+1) + \
repr(n+1)+'}$'
else:
label_string = self.name+', S'+repr(m+1) + repr(n+1)
kwargs['label'] = label_string
# plot the desired attribute vs frequency
if len (ax.patches) == 0:
smith(ax=ax, smithR = r, chart_type=chart_type, draw_labels=draw_labels, draw_vswr=draw_vswr)
ax.plot(self.s[:,m,n].real, self.s[:,m,n].imag, *args,**kwargs)
#draw legend
if show_legend:
ax.legend()
ax.axis(npy.array([-1.1,1.1,-1.1,1.1])*r)
if label_axes:
ax.set_xlabel('Real')
ax.set_ylabel('Imaginary')
[docs]def plot_it_all(self, *args, **kwargs):
r"""
Plot dB, deg, smith, and complex in subplots.
Plots the magnitude in dB in subplot 1, the phase in degrees in
subplot 2, a smith chart in subplot 3, and a complex plot in
subplot 4.
Parameters
----------
\*args : arguments, optional
passed to the matplotlib.plot command
\*\*kwargs : keyword arguments, optional
passed to the matplotlib.plot command
See Also
--------
plot_s_db - plot magnitude (in dB) of s-parameters vs frequency
plot_s_deg - plot phase of s-parameters (in degrees) vs frequency
plot_s_smith - plot complex s-parameters on smith chart
plot_s_complex - plot complex s-parameters in the complex plane
Examples
--------
>>> from skrf.data import ring_slot
>>> ring_slot.plot_it_all()
"""
plt.subplot(221)
getattr(self,'plot_s_db')(*args, **kwargs)
plt.subplot(222)
getattr(self,'plot_s_deg')(*args, **kwargs)
plt.subplot(223)
getattr(self,'plot_s_smith')(*args, **kwargs)
plt.subplot(224)
getattr(self,'plot_s_complex')(*args, **kwargs)
[docs]def stylely(rc_dict: dict = {}, style_file: str = 'skrf.mplstyle'):
"""
Loads the rc-params from the specified file (file must be located in skrf/data).
Parameters
----------
rc_dict : dict, optional
rc dict passed to :func:`matplotlib.rc`, by default {}
style_file : str, optional
style file, by default 'skrf.mplstyle'
"""
from .data import pwd # delayed to solve circular import
mpl.style.use(os.path.join(pwd, style_file))
mpl.rc(rc_dict)
def plot_calibration_errors(self, *args, **kwargs):
"""
Plot biased, unbiased and total error in dB scaled.
See Also
--------
biased_error
unbiased_error
total_error
"""
port_list = self.biased_error.port_tuples
for m,n in port_list:
plt.figure()
plt.title('S%i%i'%(m+1,n+1))
self.unbiased_error.plot_s_db(m,n,**kwargs)
self.biased_error.plot_s_db(m,n,**kwargs)
self.total_error.plot_s_db(m,n,**kwargs)
plt.ylim(-100,0)
def plot_caled_ntwks(self, attr: str = 's_smith', show_legend: bool = False, **kwargs):
r"""
Plot corrected calibration standards.
Given that the calibration is overdetermined, this may be used
as a heuristic verification of calibration quality.
Parameters
----------
attr : str
Network property to plot, ie 's_db', 's_smith', etc.
Default is 's_smith'
show_legend : bool, optional
draw a legend or not. Default is False.
\*\*kwargs : kwargs
passed to the plot method of Network
"""
ns = networkSet.NetworkSet(self.caled_ntwks)
kwargs.update({'show_legend':show_legend})
if ns[0].nports ==1:
ns.__getattribute__('plot_'+attr)(0,0, **kwargs)
elif ns[0].nports ==2:
plt.figure(figsize = (8,8))
for k,mn in enumerate([(0, 0), (1, 1), (0, 1), (1, 0)]):
plt.subplot(221+k)
plt.title('S%i%i'%(mn[0]+1,mn[1]+1))
ns.__getattribute__('plot_'+attr)(*mn, **kwargs)
else:
raise NotImplementedError
plt.tight_layout()
def plot_residuals(self, attr: str = 's_db', **kwargs):
r"""
Plot residual networks.
Given that the calibration is overdetermined, this may be used
as a metric of the calibration's *goodness of fit*
Parameters
----------
attr : str, optional.
Network property to plot, ie 's_db', 's_smith', etc.
Default is 's_db'
\*\*kwargs : kwargs
passed to the plot method of Network
See Also
--------
Calibration.residual_networks
"""
networkSet.NetworkSet(self.residual_ntwks).__getattribute__('plot_'+attr)(**kwargs)
# Network Set Plotting Commands
def animate(self, attr: str = 's_deg', ylims: Tuple = (-5, 5),
xlims: Union[Tuple, None] = None, show: bool = True,
savefigs: bool = False, dir_: str = '.', *args, **kwargs):
r"""
Animate a property of the networkset.
This loops through all elements in the NetworkSet and calls
a plotting attribute (ie Network.plot_`attr`), with given \*args
and \*\*kwargs.
Parameters
----------
attr : str, optional
plotting property of a Network (ie 's_db', 's_deg', etc)
Default is 's_deg'
ylims : tuple, optional
passed to ylim. needed to have consistent y-limits across frames.
Default is (-5 ,5).
xlims : tuple or None, optional.
passed to xlim. Default is None.
show : bool, optional
show each frame as its animated. Default is True.
savefigs : bool, optional
save each frame as a png. Default is False.
\*args, \*\*kwargs :
passed to the Network plotting function
Note
----
using `label=None` will speed up animation significantly,
because it prevents the legend from drawing
to create video paste this:
!avconv -r 10 -i out_%5d.png -vcodec huffyuv out.avi
or (depending on your ffmpeg version)
!ffmpeg -r 10 -i out_%5d.png -vcodec huffyuv out.avi
Examples
--------
>>> ns.animate('s_deg', ylims=(-5,5), label=None)
"""
was_interactive = plt.isinteractive()
plt.ioff()
for idx, k in enumerate(self):
plt.clf()
if 'time' in attr:
tmp_ntwk = k.windowed()
tmp_ntwk.__getattribute__('plot_' + attr)(*args, **kwargs)
else:
k.__getattribute__('plot_' + attr)(*args, **kwargs)
if ylims is not None:
plt.ylim(ylims)
if xlims is not None:
plt.xlim(xlims)
# rf.legend_off()
plt.draw()
if show:
plt.show()
if savefigs:
fname = os.path.join(dir_, 'out_%.5i' % idx + '.png')
plt.savefig(fname)
if savefigs:
print('\n\n')
if was_interactive:
plt.ion()
#------------------------------
#
# NetworkSet plotting functions
#
#------------------------------
[docs]def plot_uncertainty_bounds_component(
self, attribute: str,
m: Union[int, None] = None, n: Union[int, None] = None,
type: str = 'shade', n_deviations: int = 3,
alpha: float = .3, color_error: Union[str, None] = None,
markevery_error: int = 20, ax: Union[plt.Axes, None] = None,
ppf: bool = None, kwargs_error: dict = {},
*args, **kwargs):
r"""
Plot mean value of a NetworkSet with +/- uncertainty bounds in an Network's attribute.
This is designed to represent uncertainty in a scalar component of the s-parameter.
for example plotting the uncertainty in the magnitude would be expressed by,
.. math::
mean(|s|) \pm std(|s|)
The order of mean and abs is important.
Parameters
----------
attribute : str
attribute of Network type to analyze
m : int or None
first index of attribute matrix. Default is None (all)
n : int or None
second index of attribute matrix. Default is None (all)
type : str
['shade' | 'bar'], type of plot to draw
n_deviations : int
number of std deviations to plot as bounds
alpha : float
passed to matplotlib.fill_between() command. [number, 0-1]
color_error : str
color of the +- std dev fill shading. Default is None.
markevery_error : float
tbd
type : str
if type=='bar', this controls frequency of error bars
ax : matplotlib axe object
Axes to plot on. Default is None.
ppf : function
post processing function. a function applied to the
upper and lower bounds. Default is None
kwargs_error : dict
dictionary of kwargs to pass to the fill_between or
errorbar plot command depending on value of type.
\*args, \*\*kwargs :
passed to Network.plot_s_re command used to plot mean response
Note
----
For phase uncertainty you probably want s_deg_unwrap, or
similar. uncertainty for wrapped phase blows up at +-pi.
"""
if ax is None:
ax = plt.gca()
if m is None:
M = range(self[0].number_of_ports)
else:
M = [m]
if n is None:
N = range(self[0].number_of_ports)
else:
N = [n]
for m in M:
for n in N:
plot_attribute = attribute
ntwk_mean = self.__getattribute__('mean_'+attribute)
ntwk_std = self.__getattribute__('std_'+attribute)
ntwk_std.s = n_deviations * ntwk_std.s
upper_bound = (ntwk_mean.s[:, m, n] + ntwk_std.s[:, m, n]).squeeze()
lower_bound = (ntwk_mean.s[:, m, n] - ntwk_std.s[:, m, n]).squeeze()
if ppf is not None:
if type == 'bar':
raise NotImplementedError('the \'ppf\' options don\'t work correctly with the bar-type error plots')
ntwk_mean.s = ppf(ntwk_mean.s)
upper_bound = ppf(upper_bound)
lower_bound = ppf(lower_bound)
lower_bound[npy.isnan(lower_bound)] = min(lower_bound)
if ppf in [mf.magnitude_2_db, mf.mag_2_db]: # quickfix of wrong ylabels due to usage of ppf for *_db plots
if attribute == 's_mag':
plot_attribute = 's_db'
elif attribute == 's_time_mag':
plot_attribute = 's_time_db'
if type == 'shade':
ntwk_mean.plot_s_re(ax=ax, m=m, n=n, *args, **kwargs)
if color_error is None:
color_error = ax.get_lines()[-1].get_color()
ax.fill_between(ntwk_mean.frequency.f,
lower_bound.real, upper_bound.real, alpha=alpha, color=color_error,
**kwargs_error)
# ax.plot(ntwk_mean.frequency.f_scaled, ntwk_mean.s[:,m,n],*args,**kwargs)
elif type == 'bar':
ntwk_mean.plot_s_re(ax=ax, m=m, n=n, *args, **kwargs)
if color_error is None:
color_error = ax.get_lines()[-1].get_color()
ax.errorbar(ntwk_mean.frequency.f[::markevery_error],
ntwk_mean.s_re[:, m, n].squeeze()[::markevery_error],
yerr=ntwk_std.s_mag[:, m, n].squeeze()[::markevery_error],
color=color_error, **kwargs_error)
else:
raise(ValueError('incorrect plot type'))
ax.set_ylabel(Y_LABEL_DICT.get(plot_attribute[2:], '')) # use only the function of the attribute
scale_frequency_ticks(ax, ntwk_mean.frequency.unit)
ax.axis('tight')
[docs]def plot_minmax_bounds_component(self, attribute: str, m: int = 0, n: int = 0,
type: str = 'shade', n_deviations: int = 3,
alpha: float = .3, color_error: Union[str, None] = None,
markevery_error: int = 20, ax: Union[plt.Axes, None] = None,
ppf: bool = None, kwargs_error: dict = {},
*args, **kwargs):
r"""
Plots mean value of the NetworkSet with +/- uncertainty bounds in an Network's attribute.
This is designed to represent uncertainty in a scalar component of the s-parameter. For example
plotting the uncertainty in the magnitude would be expressed by
.. math::
mean(|s|) \pm std(|s|)
The order of mean and abs is important.
Parameters
----------
attribute : str
attribute of Network type to analyze
m : int
first index of attribute matrix
n : int
second index of attribute matrix
type : str
['shade' | 'bar'], type of plot to draw
n_deviations : int
number of std deviations to plot as bounds
alpha : float
passed to matplotlib.fill_between() command. [number, 0-1]
color_error : str
color of the +- std dev fill shading. Default is None.
markevery_error : float
tbd
type : str
if type=='bar', this controls frequency of error bars
ax : matplotlib axe object
Axes to plot on. Default is None.
ppf : function
post processing function. a function applied to the
upper and lower bounds. Default is None
kwargs_error : dict
dictionary of kwargs to pass to the fill_between or
errorbar plot command depending on value of type.
\*args, \*\*kwargs :
passed to Network.plot_s_re command used to plot mean response
Note
----
For phase uncertainty you probably want s_deg_unwrap, or
similar. Uncertainty for wrapped phase blows up at +-pi.
"""
if ax is None:
ax = plt.gca()
ntwk_mean = self.__getattribute__('mean_'+attribute)
ntwk_std = self.__getattribute__('std_'+attribute)
lower_bound = self.__getattribute__('min_'+attribute).s_re[:,m,n].squeeze()
upper_bound = self.__getattribute__('max_'+attribute).s_re[:,m,n].squeeze()
if ppf is not None:
if type =='bar':
raise NotImplementedError('the \'ppf\' options don\'t work correctly with the bar-type error plots')
ntwk_mean.s = ppf(ntwk_mean.s)
upper_bound = ppf(upper_bound)
lower_bound = ppf(lower_bound)
lower_bound[npy.isnan(lower_bound)]=min(lower_bound)
if ppf in [mf.magnitude_2_db, mf.mag_2_db]: # quickfix of wrong ylabels due to usage of ppf for *_db plots
if attribute == 's_mag':
attribute = 's_db'
elif attribute == 's_time_mag':
attribute = 's_time_db'
if type == 'shade':
ntwk_mean.plot_s_re(ax=ax,m=m,n=n,*args, **kwargs)
if color_error is None:
color_error = ax.get_lines()[-1].get_color()
ax.fill_between(ntwk_mean.frequency.f,
lower_bound, upper_bound, alpha=alpha, color=color_error,
**kwargs_error)
#ax.plot(ntwk_mean.frequency.f_scaled,ntwk_mean.s[:,m,n],*args,**kwargs)
elif type =='bar':
raise (NotImplementedError)
ntwk_mean.plot_s_re(ax=ax, m=m, n=n, *args, **kwargs)
if color_error is None:
color_error = ax.get_lines()[-1].get_color()
ax.errorbar(ntwk_mean.frequency.f[::markevery_error],
ntwk_mean.s_re[:,m,n].squeeze()[::markevery_error],
yerr=ntwk_std.s_mag[:,m,n].squeeze()[::markevery_error],
color=color_error,**kwargs_error)
else:
raise(ValueError('incorrect plot type'))
ax.set_ylabel(Y_LABEL_DICT.get(attribute[2:], '')) # use only the function of the attribute
scale_frequency_ticks(ax, ntwk_mean.frequency.unit)
ax.axis('tight')
[docs]def plot_uncertainty_bounds_s_db(self, *args, **kwargs):
"""
Call ``plot_uncertainty_bounds(attribute='s_mag','ppf':mf.magnitude_2_db*args,**kwargs)``.
See plot_uncertainty_bounds for help.
"""
kwargs.update({'attribute':'s_mag','ppf':mf.magnitude_2_db})
self.plot_uncertainty_bounds_component(*args,**kwargs)
[docs]def plot_minmax_bounds_s_db(self, *args, **kwargs):
"""
Call ``plot_uncertainty_bounds(attribute= 's_mag','ppf':mf.magnitude_2_db*args,**kwargs)``.
See plot_uncertainty_bounds for help.
"""
kwargs.update({'attribute':'s_mag','ppf':mf.magnitude_2_db})
self.plot_minmax_bounds_component(*args,**kwargs)
[docs]def plot_minmax_bounds_s_db10(self, *args, **kwargs):
"""
Call ``plot_uncertainty_bounds(attribute= 's_mag','ppf':mf.magnitude_2_db*args,**kwargs)``.
see plot_uncertainty_bounds for help
"""
kwargs.update({'attribute':'s_mag','ppf':mf.mag_2_db10})
self.plot_minmax_bounds_component(*args,**kwargs)
[docs]def plot_uncertainty_bounds_s_time_db(self, *args, **kwargs):
"""
Call ``plot_uncertainty_bounds(attribute= 's_mag','ppf':mf.magnitude_2_db*args,**kwargs)``.
See plot_uncertainty_bounds for help.
"""
kwargs.update({'attribute':'s_time_mag','ppf':mf.magnitude_2_db})
self.plot_uncertainty_bounds_component(*args,**kwargs)
[docs]def plot_minmax_bounds_s_time_db(self, *args, **kwargs):
"""
Call ``plot_uncertainty_bounds(attribute= 's_mag','ppf':mf.magnitude_2_db*args,**kwargs)``.
See plot_uncertainty_bounds for help.
"""
kwargs.update({'attribute':'s_time_mag','ppf':mf.magnitude_2_db})
self.plot_minmax_bounds_component(*args, **kwargs)
def plot_uncertainty_decomposition(self, m: int = 0, n: int = 0):
"""
Plot the total and component-wise uncertainty.
Parameters
----------
m : int
first s-parameters index
n :
second s-parameter index
"""
if self.name is not None:
plt.title(r'Uncertainty Decomposition: %s $S_{%i%i}$'%(self.name,m,n))
self.std_s.plot_s_mag(label='Distance', m=m,n=n)
self.std_s_re.plot_s_mag(label='Real', m=m,n=n)
self.std_s_im.plot_s_mag(label='Imaginary', m=m,n=n)
self.std_s_mag.plot_s_mag(label='Magnitude', m=m,n=n)
self.std_s_arcl.plot_s_mag(label='Arc-length', m=m,n=n)
[docs]def plot_uncertainty_bounds_s(self, multiplier: float = 200, *args, **kwargs):
"""
Plot complex uncertainty bounds plot on smith chart.
This function plots the complex uncertainty of a NetworkSet
as circles on the smith chart. At each frequency a circle
with radii proportional to the complex standard deviation
of the set at that frequency is drawn. Due to the fact that
the `markersize` argument is in pixels, the radii can scaled by
the input argument `multiplier`.
default kwargs are
{
'marker':'o',
'color':'b',
'mew':0,
'ls':'',
'alpha':.1,
'label':None,
}
Parameters
----------
multiplier : float
controls the circle sizes, by multiples of the standard
deviation.
"""
default_kwargs = {
'marker':'o',
'color':'b',
'mew':0,
'ls':'',
'alpha':.1,
'label':None,
}
default_kwargs.update(**kwargs)
if plt.isinteractive():
was_interactive = True
plt.interactive(0)
else:
was_interactive = False
[self.mean_s[k].plot_s_smith(*args, ms = self.std_s[k].s_mag*multiplier, **default_kwargs) for k in range(len(self[0]))]
if was_interactive:
plt.interactive(1)
plt.draw()
plt.show()
[docs]def plot_logsigma(self, label_axis: bool = True, *args,**kwargs):
r"""
Plot the uncertainty for the set in units of log-sigma.
Log-sigma is the complex standard deviation, plotted in units
of dB's.
Parameters
----------
label_axis : bool, optional
Default is True.
\*args, \*\*kwargs : arguments
passed to self.std_s.plot_s_db()
"""
self.std_s.plot_s_db(*args,**kwargs)
if label_axis:
plt.ylabel('Standard Deviation(dB)')
[docs]def signature(self, m: int = 0, n: int = 0, component: str = 's_mag',
vmax: Union[Number, None] = None, vs_time: bool = False,
cbar_label: Union[str, None] = None,
*args, **kwargs):
r"""
Visualization of a NetworkSet.
Creates a colored image representing the some component
of each Network in the NetworkSet, vs frequency.
Parameters
------------
m : int, optional
first s-parameters index. Default is 0.
n : int, optional
second s-parameter index. Default is 0.
component : ['s_mag','s_db','s_deg' ..]
scalar component of Network to visualize. should
be a property of the Network object.
vmax : number or None.
sets upper limit of colorbar, if None, will be set to
3*mean of the magnitude of the complex difference.
Default is None.
vs_time: Boolean, optional.
if True, then we assume each Network.name was made with
rf.now_string, and we make the y-axis a datetime axis.
Default is False.
cbar_label: String or None, optional
label for the colorbar. Default is None
\*args,\*\*kw : arguments, keyword arguments
passed to :func:`~pylab.imshow`
"""
mat = npy.array([self[k].__getattribute__(component)[:, m, n] \
for k in range(len(self))])
# if vmax is None:
# vmax = 3*mat.mean()
if vs_time:
# create a datetime index
dt_idx = [now_string_2_dt(k.name) for k in self]
mpl_times = date2num(dt_idx)
y_max = mpl_times[0]
y_min = mpl_times[-1]
else:
y_min = len(self)
y_max = 0
# creates x and y scales
freq = self[0].frequency
extent = [freq.f_scaled[0], freq.f_scaled[-1], y_min, y_max]
# set default imshow kwargs
kw = {'extent': extent, 'aspect': 'auto', 'interpolation': 'nearest',
'vmax': vmax}
# update the users kwargs
kw.update(kwargs)
img = plt.imshow(mat, *args, **kw)
if vs_time:
ax = plt.gca()
ax.yaxis_date()
# date_format = plt.DateFormatter('%M:%S.%f')
# ax.yaxis.set_major_formatter(date_format)
# cbar.set_label('Magnitude (dB)')
plt.ylabel('Time')
else:
plt.ylabel('Network #')
plt.grid(0)
freq.labelXAxis()
cbar = plt.colorbar()
if cbar_label is not None:
cbar.set_label(cbar_label)
return img
[docs]def plot_circuit_graph(self, **kwargs):
"""
Plot the graph of the circuit using networkx drawing capabilities.
Customisation options with default values:
::
'network_shape': 's'
'network_color': 'gray'
'network_size', 300
'network_fontsize': 7
'inter_shape': 'o'
'inter_color': 'lightblue'
'inter_size', 300
'port_shape': '>'
'port_color': 'red'
'port_size', 300
'port_fontsize': 7
'edges_fontsize': 5
'network_labels': False
'edge_labels': False
'inter_labels': False
'port_labels': False
'label_shift_x': 0
'label_shift_y': 0
"""
# Get the circuit graph. Will raise an error if the networkx package
# is not installed.
G = self.G
# default values
network_labels = kwargs.pop('network_labels', False)
network_shape = kwargs.pop('network_shape', 's')
network_color = kwargs.pop('network_color', 'gray')
network_fontsize = kwargs.pop('network_fontsize', 7)
network_size = kwargs.pop('network_size', 300)
inter_labels = kwargs.pop('inter_labels', False)
inter_shape = kwargs.pop('inter_shape', 'o')
inter_color = kwargs.pop('inter_color', 'lightblue')
inter_size = kwargs.pop('inter_size', 300)
port_labels = kwargs.pop('port_labels', False)
port_shape = kwargs.pop('port_shape', '>')
port_color = kwargs.pop('port_color', 'red')
port_size = kwargs.pop('port_size', 300)
port_fontsize = kwargs.pop('port_fontsize', 7)
edge_labels = kwargs.pop('edge_labels', False)
edge_fontsize = kwargs.pop('edge_fontsize', 5)
label_shift_x = kwargs.pop('label_shift_x', 0)
label_shift_y = kwargs.pop('label_shift_y', 0)
# sort between network nodes and port nodes
all_ntw_names = [ntw.name for ntw in self.networks_list()]
port_names = [ntw_name for ntw_name in all_ntw_names if 'port' in ntw_name]
ntw_names = [ntw_name for ntw_name in all_ntw_names if 'port' not in ntw_name]
# generate connecting nodes names
int_names = ['X'+str(k) for k in range(self.connections_nb)]
fig, ax = plt.subplots(figsize=(10,8))
pos = nx.spring_layout(G)
# draw Networks
nx.draw_networkx_nodes(G, pos, port_names, ax=ax,
node_size=port_size,
node_color=port_color, node_shape=port_shape)
nx.draw_networkx_nodes(G, pos, ntw_names, ax=ax,
node_size=network_size,
node_color=network_color, node_shape=network_shape)
# draw intersections
nx.draw_networkx_nodes(G, pos, int_names, ax=ax,
node_size=inter_size,
node_color=inter_color, node_shape=inter_shape)
# labels shifts
pos_labels = {}
for node, coords in pos.items():
pos_labels[node] = (coords[0] + label_shift_x,
coords[1] + label_shift_y)
# network labels
if network_labels:
network_labels = {lab:lab for lab in ntw_names}
nx.draw_networkx_labels(G, pos_labels, labels=network_labels,
font_size=network_fontsize, ax=ax)
# intersection labels
if inter_labels:
inter_labels = {'X'+str(k):'X'+str(k) for k in range(self.connections_nb)}
nx.draw_networkx_labels(G, pos_labels, labels=inter_labels,
font_size=network_fontsize, ax=ax)
# port labels
if port_labels:
port_labels = {lab:lab for lab in port_names}
nx.draw_networkx_labels(G, pos_labels, labels=port_labels,
font_size=port_fontsize, ax=ax)
# draw edges
nx.draw_networkx_edges(G, pos, ax=ax)
if edge_labels:
edge_labels = self.edge_labels
nx.draw_networkx_edge_labels(G, pos,
edge_labels=edge_labels, label_pos=0.5,
font_size=edge_fontsize, ax=ax)
# remove x and y axis and labels
plt.axis('off')
plt.tight_layout()
[docs]def plot_contour(freq: frequency.Frequency,
x: NumberLike, y: NumberLike, z: NumberLike,
min0max1: int, graph: bool = True,
cmap: str = 'plasma_r', title: str = '',
**kwargs):
r"""
Create a contour plot.
Parameters
----------
freq : :skrf.Frequency:
Frequency object.
x : array
x points
y : array
y points.
z : array
z points.
min0max1 : int
0 for min, 1 for max.
graph : bool, optional
plot graph if True. The default is True.
cmap : str, optional
Colormap label. The default is 'plasma_r'.
title : str, optional
Figure title. The default is ''.
\*\*kwargs : dict
Other parameters passed to `matplotlib.plot()`.
Returns
-------
GAMopt : :skrf.Network:
Network
VALopt : float
min or max.
"""
ri = npy.linspace(0,1, 50)
ti = npy.linspace(0,2*npy.pi, 150)
Ri , Ti = npy.meshgrid(ri, ti)
xi = npy.linspace(-1,1, 50)
Xi, Yi = npy.meshgrid(xi, xi)
triang = tri.Triangulation(x, y)
interpolator = tri.LinearTriInterpolator(triang, z)
Zi = interpolator(Xi, Yi)
if min0max1 == 1 :
VALopt = npy.max(z)
else :
VALopt = npy.min(z)
GAMopt = network.Network(f=[freq], s=x[z==VALopt] +1j*y[z==VALopt])
if graph :
fig, ax = plt.subplots(**kwargs)
an = npy.linspace(0, 2*npy.pi, 50)
cs, sn = npy.cos(an), npy.sin(an)
plt.plot(cs, sn, color='k', lw=0.25)
plt.plot(cs, sn*0, color='g', lw=0.25)
plt.plot((1+cs)/2, sn/2, color='k', lw=0.25)
plt.axis('equal')
ax.set_axis_off()
ax.contour(Xi, Yi, Zi, levels=20, vmin=Zi.min(), vmax= Zi.max(), linewidths=0.5, colors='k')
cntr1 = ax.contourf(Xi, Yi, Zi, levels=20, vmin=Zi.min(), vmax= Zi.max(),cmap=cmap)
fig.colorbar(cntr1, ax=ax)
ax.plot(x, y, 'o', ms=0.3, color='k')
ax.set(xlim=(-1, 1), ylim=(-1, 1))
plt.title(title)
plt.show()
return GAMopt, VALopt