在分析拉扎维《模拟CMOS集成电路》p48页采用二极管连接负载共源级电路时,书中提到此结构会严重限制输出电压的摆幅,由于没有直观的图像分析,且举的例子不怎么好,我一直未能理解。故想要通过建模的方式对其进行探究。
二极管连接负载共源放大电路输出电压摆幅与放大倍数的关系建模分析
将二极管连接负载共源级电路的输出电压V_out随输入电压V_in的变化与该结构的放大倍数的关系绘制成图像,为简化分析,当M1管或M2管进入三极管区或截止区的时候,便将V_out直接置零或拉至VDD,作图如下:
从该图便很容易发现带二极管连接负载共源放大电路输出电压摆幅的巨大限制,当放大倍数Av上升时,放大器处于放大区的范围越来越小。根据书中阐述,其主要原因在于M1管与M2管的过驱动电压也成Av倍关系,这使得放大倍数Av不能够设计的太高。
可这样的图无法直观地分析在各个不同输入电压下两管过驱动电压之间的关系。如果能够有这样一种机制,当我点击函数图像上的一个点,便可以得出M1管与M2管的过驱动电压,那岂不是更加方便?
带着这个想法,我开始探究将函数绘制为点选式交互图像的途径。
例程代码分析
可以利用mpl-interactions中的scatter_selector widget功能绘制可点选式交互的函数图像。首先分析其官方例程。
参考:https://mpl-interactions.readthedocs.io/en/stable/examples/scatter-selector.html
import pickle
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
import mpl_interactions.ipyplot as iplt
from mpl_interactions import indexer, panhandler, zoom_factory
from mpl_interactions.utils import indexer
from mpl_interactions.widgets import scatter_selector_index
pickle库能够实现任意对象与文本之间的相互转化,也可以实现任意对象与二进制之间的相互转化。它的主要用处是实现python对象的存储与恢复。
pickle 模块提供了以下 4 个函数供我们使用:
- dumps():将 Python 中的对象序列化成二进制对象,并返回;
- loads():读取给定的二进制对象数据,并将其转换为 Python 对象;
- dump():将 Python 中的对象序列化成二进制对象,并写入文件;
- load():读取指定的序列化数据文件,并返回对象。
with open("stock-metadata.pickle", "rb") as f:
meta = pickle.load(f)
prices = np.load("stock-prices.npz")["prices"]
names = meta["names"]
good_idx = meta[
"good_idx"
] # only plot the ones for which we were able to parse sector info
data_colors = meta["data_colors"]
第一句行为python的with as语句,其设计到python的上下文管理器知识点,什么是上下文管理器?以本例为例,就是当python读取文件出现异常时,上下文管理器提供了一个机制可以释放掉占用的内存与资源。具体解释可参考这篇博文:http://c.biancheng.net/view/5319.html
meta对象为一个大小为4的字典,names、good_idx、data_colors完成了对这个字典元素的索引。
# calculate the daily price difference
price_changes = np.diff(prices)
# Below is a pretty standard way of normalizing numerical data
normalized_price_changes = price_changes - price_changes.mean(axis=-1, keepdims=True)
normalized_price_changes /= price_changes.std(axis=-1, keepdims=True)
# calculate the covariance matrix
covariance = np.cov(normalized_price_changes.T)
# Calculate the eigenvectors (i.e. the principle components)
evals, evecs = np.linalg.eig(covariance)
evecs = np.real(evecs)
# project the companies onto the principle components
transformed = normalized_price_changes @ evecs
其中@表示矩阵乘法运算符
x, y = transformed[good_idx][:, 0], transformed[good_idx][:, 1]
执行前,可以发现transformed变量规格如下:
且用于索引的good_idx也是一个向量:
而输出x,y的规格如下:
可以看见输出的x,y的规格和transformed的第一列和第二列的规格并不一致。
这涉及到numpy库的一个用法,即用向量作为索引,返回的是索引元素组成的新向量,参考:Numpy | Indexing - GeeksforGeeks
例如:
输出结果为:
本例中用于索引的向量good_idx为布尔值向量,这种情况下,返回的向量将只包含对应值为True的元素。
测试如下:
import numpy as np
A_bool = np.array([True, False, True, True, False])
A = np.array([1,2,3,4,5])
B = A[A_bool]
print(B)
需要注意的是,用于索引的布尔型向量规格必须与源向量规格一致。
fig, axs = plt.subplots(1, 2, figsize=(20, 10), gridspec_kw={"width_ratios": [1.5, 1]})
这里figsize用于设定图片大小,gridspec_kw方法用于设定plot在一张图像上的两张图的宽度比率。
index = scatter_selector_index(axs[0], x, y, c=data_colors, cmap="tab20")
接下来便是点选式交互图像的核心部分,scatter_selector_index函数可以用于绘制散点图,同时赋予散点图可点选机制。本句即在图像的左侧绘制出散点图,同时将点选的散点的index作为变量储存起来。
# plot all the stock traces in light gray
plt.plot(prices.T, color="k", alpha=0.05)
# add interactive components to the subplot on the right
# note the use of indexer
controls = iplt.plot(indexer(prices), idx=index, color="r")
iplt.title(indexer(names), controls=controls["idx"])
第一句用于在图像右侧绘制灰色股市行情走线。而第二句则调用mpl_interactions.ipyplot库,将scatter_selector_index点选事件与绘制红色走线事件相联系,在灰色走线中选择点选事件对应的函数走线用到的函数是indexer(),该函数能将为向量添加可交互的index,从而与点选的散点进行对应。最后一句则是将names向量进行编号,同时将其与点选事件联系起来。
为了更好说明indexer的作用,下面这个例程列出了两种相同的写法。
python绘制可点选式交互的函数图像测试
为了实际测试这两个函数的用法,写一段测试代码测试点选机制:
x = np.array([1,2,5,6,7])
y = np.array([4,5,6,7,8])
names = list(["a","b","c","d","e"])
fig = plt.figure()
ax = fig.subplots() # Create a figure and an axes.
index = scatter_selector_index(ax, x, y)
iplt.title(indexer(names), idx=index)
其中names为一个包含字符串元素的数组,后面用indexer()函数可以实现对该数组元素的编号,使其也能与点选事件进行互动。
我希望能让我们点选的点高亮地显示出来,这该怎样做呢?
目前的方法是加入语句:
iplt.scatter(indexer(x.T),indexer(y.T), idx=index, c='r')
即使用indexer将(x,y)坐标点也进行编号,并重新在坐标图上用大红点绘制出来。
另外,我还希望能将点选的点的坐标值能直观地显示出来,经过一番探寻,我发现这里可以使用scatter_selector_index类的onchanged方法,当我们点选散点的时候,其可以向我们的更新函数返回点选散点的编号,从而使更新函数能够更新t1, t2文本框中的值。
t1 = ax.text(-0.3, -0.35, 'x = %.2f'%x[0], style='italic', fontsize=12,
bbox={'facecolor': 'grey', 'alpha': 0.5, 'pad': 10}, transform=ax.transAxes)
t2 = ax.text(-0.3, -0.2, 'y = %.2f'%y[0], style='italic', fontsize=12,
bbox={'facecolor': 'grey', 'alpha': 0.5, 'pad': 10}, transform=ax.transAxes)
def update_text(idx):
t1.set_text('x = %.2f'%x[idx])
t2.set_text('y = %.2f'%y[idx])
index.on_changed(update_text)
最终效果如图所示:
如此,我便可以重新绘制二极管连接负载共源放大电路输出电压摆幅的分析图了。