仿真电路46、47、48(SLOC: 6354)

前几天感冒了,天昏地暗实在坚持不住,没有写开发日志,但是每天依然坚持写了几行代码。 主要完成的是将电容IC引入到当前的仿真体系中来。实际上在日志45中已经将它引入进来了,但是那个时候只是“初步引入”,对于它的的电压、电流的计算是错误的,所以这几天主要是将电容的电压、电流都逐步调整正确了。 电压网络的求解其实改动并不大,因为电容本身的阻抗计算与电阻是相似的,所以对于电压网络而言,任何一个电容、就和任何一个电阻没有差别。如果不考虑电容的初始电压,那么电容就是电阻。 电容本身的初始电压是唯一的区别,因而在构建矩阵时将其初始电压也当作一个超级电源考虑就可以了。 如上完成电压网络求解之后,接下来求解电流。 电容器的电流需要用自身的历史电压做“微分”完成计算。而在离散的仿真环境中,这个“微分”是利用“差分”近似代替出来的。在使用差分近似代替微分运算时,有2种常用的替代方法,分别是题型近似法、后向欧拉法。这两种统称为“数值计算”的计算方式,都可以完成电容电流的计算。 两种数值计算方法各有利弊,不同的仿真参数下,两种方案的选择不同。好在它们的实现都很简单,可以全部实现出来,然后选择使用。但是我还没有做梯形近似,只先用后向欧拉法完成了电流的计算。 虽然现在实现的IC还非常少,但是已经可以构建出非常简单、基础的电路了,所以接下来将完成以下功能的开发:1、电流移动动画;2、示波器;3、文件存取;4、物理单刀开关IC。

仿真软件开发进展

Day: 44, SLOC: 6182。 前阵子将仿真软件的基本开发环境搭建起来、勾勒了个整体的模型结构,并且先有的没的将符号绘制、图像渲染、参数调整、计算输出……都草草实现了一遍,绝大多数都是以快速成型、不确保正确为原则开拓的代码基底。 这套开发环境的构建原则是“尽量轻巧”,因而甚至连IDE工具、CMake都没有引入,纯粹使用裸代码撰写,虽然过程痛苦,换来的却是十分的轻便,而且每一行代码都是纯手写,代码敲得十分过瘾。 上面的快乐时光结束之后,就是实打实的撰写每一个具体方法,再不能天马行空、一日百行。每天代码增长大约只有几十行。好在我对模块拆分的很细,基本上每一天都能有个小小的新功能被实现出来。 今天完成的是AC电源的引入,现在终于可以看到一张“时域动画”,效果比之前每天看到的“直流稳态”要灵动一些。 接下来要完成的是电容的引入,这是一个比较重要的、也是需要用到更多核心计算机制的IC,一旦完成了电容的引入,就可以进行一些基本电路的仿真了。 当然,电容的引入并不是1、2天就能完成的事情,所以这期间还会找一些相对容易的功能也一起实现。例如:对已经绘制了的线段、IC进行删除、移动。这样以上普通功能和“难点功能”,就共同构成了本周要进行的开发需求。

sncircuit开发备忘:网络合并

在 NetlistManagerClass 中原本有一个方法叫做 updateAllNetlistIndexTo(),它起初的含义是将第一网络节点中所有的成员,从第一网络节点中删除、并且追加到第二网络节点中,然后第一网络节点便成为了“荒废节点”,可以删除掉了。 这个方法存在着一些问题。首先是它的名称今天看来已经不再准确,更准确的名称应该是 merge,完成的功能还是一样的:将网络节点a和网络节点b中的所有成员,都迁移到a或b网络节点中,然后将荒废掉的网络节点剔除掉。 其次,对于merge(netlist a, netlist b),究竟是将谁的内容迁移到另外一个网络节点中呢?昨天我纠结了很久,后来想明白了:无论从a到b,还是从b到a,实际上都是一样的,因而完全没有必要纠结要不要为这个方法增加第三个参数指明保留谁。 而今天重构这块代码的时候,又有了更为“智能”的一个方案:既然无论怎样的方向对于合并都没有影响,那么就应该选择成员最少的网络节点进行“搬家”操作,这样效率会更高,因而在merge()内部进行判断,找到a和b中迁移成本低的进行迁移。 额外的,在完成上述方法重构时发现,除了在 PlacementManagerClass 中会调用netlistManager->merge()方法外,在NetlistManagerClass中也会调用netlistManager->merge()方法,并且后者的调用是“迂回到footpin中”进行调用,后者的“迂回”导致代码逻辑十分难以理解,所以将迂回的调用删除了,如此就简化到只要在PlacementManagerClass和NetlistManagerClass两个类中的两个位置上调用merge(),如此代码相对比较容易理解。 即便做到了上面说的“只在两个地方调用”,实际上还是有些“繁琐”的,还是业务逻辑上的繁琐与不恰当。这一点上现在脑子有些乱,所以先写下这篇备忘,将相关工作记录一下。有时间会再推敲如何继续合并代码,争取只在一个地方、一个逻辑内,完成“网络节点的合并”。

Simple Nano Circuit 开发日记(32)

这个软件截至昨天,应该当是一个小的阶段完成——可以初步实现直流电源、电阻构成的电路中各个网络节点的电压求解。起初觉得神秘,做过一遍发现背后的数学原理十分简单,只需要根据电路创建线性方程组,然后求解即可。 下一阶段知识难度估计会大幅度提升,为了迎接下一阶段的挑战,我想还是应该先把当前的工作重新整理、封装,确保现在的代码稳固。因而今天开始做代码的收敛工作。 首先是将之前遗留的若干问题要逐一解决掉,其中既有bug,也有功能的缺失。今天主要完成了以下工作: 1、线性方程组构建中存在一个缺陷,引发了在开路时的电压计算错误,这是方程组中的节点方程构建时,没有考虑到悬空引脚导致的; 2、对代码中若干冗余的方法进行清理; 3、对代码中若干传值的地方进行调整,改成传递引用; 除此之外,还有一些功能上的缺失也要在接下来几天逐一完成: 1、对已放置的IC进行删除、移动等操作; 2、对已放置的IC进行选取,通过弹窗对它们的属性值进行调整。 如上工作全部完成之后,便可以将“交流电源”引入进来,从而产生“时间”的概念,为后续增加非线性元件打下初步基础。 Update 2024.12.28 Day: 33 SLOC: 5012 下午到了家乐福,晚上吃的牛肉面。吃牛肉面之前的一段时间: 1、完成了弹窗修改 电阻 和 直流电源电压 的功能; 2、完成了这个弹窗机制的通用化,也就是说所有的IC,都从基类定义是否可以弹窗,如果可以弹窗,就在IC自己的类内实现弹窗风格和窗口内容设置; 然后下楼吃牛肉面,去的路上想明白了线路中的电流是怎么计算的,没有偷懒的途径,必须对线段进行分割,才能实现电流的分段计算。所以吃完牛肉面回来,开始着手进行线路分割操作。 分割线路的整体思想很简单:落PIN时,判断落PIN是否压中了线段,如果压中线段则对已压中的线段按PIN进行切割;画线时,则是判断线段是否穿越了PINs,如果穿越了PINs,就一次找到每一个PIN同样以PIN为分割点、对线段进行分割。 完成这个功能之后,又进行了一下“清理电路中冗余线段”的功能实现。 1、如果某线段两端有任意段悬空,则将这条线段标记为冗余线段,完成一次检测之后,将所有检测到的冗余线段删除掉(注意此时还要删除连带的可能的冗余网络节点、冗余网络节点存储器等)。…

Simple Nano Circuit 开发日记(30)

Day: 30, SLOC: 4614。 ​最近几天小仿真软件写的有些怠慢,代码膨胀比较厉害。好在终于将主要想法实现了出来,接下来将用2-3天填补遗留的若干缺陷、再花上一周左右做一做代码收敛和文档补全。之后便可以继续下一阶段的开发了。接下来将完成交流电源,再把电感和电容加进来。功能开发的脉络虽然清晰,但实际上这对我而言还是非常有挑战的:当电路只有电阻时,仿真求解只需用到线性代数,可一旦引入了电容和电感,将会用到微分的知识,期望用到的知识点不会太多、太难。 Day: 31, SLOC: 4635。 今天代码与昨天比起来,增加的并不多,但实际上也是做了一些工作的,因为主要是对原有代码进行修缮,所以单纯代码行数上看不出来什么。但借助 git diff 可以看出来,今天也是进行了200多行代码的调整。 主要是解决了此前遗留的奇异矩阵求解问题。 当矩阵是奇异矩阵时,程序会尝试去求解一个元,完成这一个元的求解之后,再次判定矩阵是否是奇异矩阵:如果依然是,则放弃此次求解;如果已经不再是奇异矩阵,就进行求解。 上面这个是之前的思路,然而上面这个思路其实是存在问题的,正确的做法是:如果奇异矩阵在完成了一次解奇异之后,依然是奇异矩阵,可以再次尝试解奇异:如此反复解无可解才认为矩阵无解。若是经过了多次解奇异之后满秩了,那么就可以求解了。 从以上错误的做法到正确的做法,就是今天主要完成的工作。 除此之外还做了一些其他的调整:为 Pin 引脚增加了一些属性,这样在进行相关的运算时就不用再通过引脚找到它的 IC 再去做 IC 判定,引脚在创建时就已经将 IC 信息记录下来,用的时候直接询问就可以了。 同时,引脚不仅记录着…

Simple Nano Circuit 开发日记(29)

Day: 24, SLOC: 3500。​与其使用完善的、成熟的、现成的仿真软件,不如自己半学半写的实现一个,这样一来可以学习C++编程语言;二来可以把自己荒废了多年的数学重新捡起来看一看、读一读;三来能对电子元件的特性有更全面的了解。这个小软件没有甲方,做起来也就更有乐趣、更从容一些,我考虑就是尽量不引入太多第三方依赖,让它尽可能的小一些。 Day: 29, SLOC: 4263。完成了对 MathMatrixClass 的独立封装,因为增加了对矩阵的分块、奇异性判断,所以可以得到整个系统中的电路彼此不连通的各个局部电路。又通过奇异矩阵判定出电路无解,通过解奇异尝试,若能解除奇异便可再次尝试求解。若无法解除奇异、或者无论如何都无法求解,也不会终止程序,就对那块子矩阵不处理就好了,能求解的部分求解出来。 这样做的好处是更接近物理现实,也许对于成熟的仿真软件,它们还有更复杂的考量,但是以我现在的认知而言,我觉得当前的做法更接近物理现实一些。 因为加入了解奇异尝试,所以已经可以正确的推导出网络节点的正确电压,我并没有在没有“地”的时候默认电源负极为地,而是通过解奇异之后,依然有程序去尝试矩阵求解,这样的出来各个节点电压,看上去效果也是正确的。

SDL2中纹理旋转的小困惑

这几天使用SDL2练手一个小应用,其中有个需求是期望对纹理进行旋转,结果发现:不旋转时显示正常,一旦旋转之后输出的图形就会发生很严重的扭曲。 之所以产生这个现象,是因为我创建的这个纹理并不是正方形,而是一个长和宽不相同的矩形(长方形)。现在虽然找到了原因,也通过临时创建了一个正方形来暂且解决了问题,但心中总难免觉得“膈应”。还是期望能够有更正确的解决方案,以正确实现这个需求。 在写完上面的“备忘”之后,又经过了一天的时间,现在已经对长方形的旋转进行了两次改良,感觉应该是已经基本正确、完美的实现了对长方形纹理的旋转。以下是这篇文章的思想脉络: 以下是详细的记录: 一、无法正确完成长方形纹理旋转的现象 在SDL2中,创建了一个32*16的纹理,暂且称这个纹理为“一杆长枪”吧。这个“一杆长枪”的纹理本来是水平渲染到屏幕上的,看上去就是一把图像准确的“长枪”。 我期望是使用键盘的M按键控制纹理旋转,我的旋转策略非常简单,每次按下M按键,只会顺时针产生一次90度的旋转,因而长枪或者水平出现在屏幕上、或者就是竖直的出现在屏幕上。这个需求并不复杂,也没有必要考虑其他刁钻的角度,感觉应该很容易实现。 然而当我完成了代码的撰写之后,长枪水平出现在屏幕上时,的确是正确的。可一旦旋转了90度之后,长枪并没有如预期的竖直的出现在屏幕上,而是竖直、却被压扁了的出现在了屏幕上——渲染结果是竖直的“手枪”出现在了屏幕上。 在反复的胡乱修改代码过程中,还有一些时候会出现如下各种更加离谱的情况: 当然以上更加离谱的情形实际上是我的代码完全不正确导致的。实际上代码如果正确,只可能出现一种情况:水平时是长枪、竖直时是短枪。 二、导致问题出现的原因 经过将近一天的排查,最终才搞清楚原因:SDL_RenderCopyEx()方法虽然的确可以完成旋转,但是它在不指定原纹理、新纹理的尺寸时,是会自动进行一定的缩放的——它会依渲染目标区域先对图像进行缩放、然后才进行旋转操作。 网上之所以没有这个问题的讨论,是因为大家或者没有我这样的需求、或者就是有我这种需求但实现的都是正确的,没有人入坑、自然也就不会有人讨论。 我的需求有一个“坑”:纹理并不是直接绘制在“屏幕渲染区域上”,而是纹理渲染在“与纹理相同的渲染区域上”。这个细微的差异引起了我所遇到的问题。 如果纹理是被渲染在屏幕上,屏幕一般都是在640×480以上,显然的:对于一杆32×16的长方形“长枪”,无论怎样旋转,都会在640×480的内部展现出来,不会发生扭曲。 然而如果期望的是:将32×16的长枪,渲染到16×32的渲染区中,就会出现各种各样的问题了: 起初:是将32×16的长枪,渲染到32×16的渲染区中,这个时候长枪不用进行旋转,显示正常:水平的长枪; 然后按下了键盘上的M按键,程序开始对长枪进行旋转处理:我的程序逻辑是:创建一个新的16×32的渲染区域,然后将32×16的长枪通过SDL_RenderCopyEx()复制进入16×32的渲染区域、并同时进行90度的旋转。此时,SDL_RenderCopyEx()内部的操作很可能是:先将32×16的纹理资源贴入16×32的渲染区域中,因为没有指定缩放,所以它自行进行缩放,将长枪从32×16缩放成了16×8,然后再进行旋转,最终得到了一个8×16的竖直图像并呈现在16×32的渲染区域中。 上述描述不够直观、但我也不想额外画图说明这个事情了,总之大概意思就是这样——旋转之后的图案总是会出现各种各样的扭曲情形。 三、粗糙的、简易的解决方案 知道了原因,解决起来就比较容易了,但实际上也并不是一帆风顺的。我起初是退而求其次的放弃了32×16这样的“长方形”,而是将软件中用到的所有图形,都做成了32×32的图形,这样无论怎样旋转(只要不出现30度、45度等非直角角度),一定可以正常显示。 然而这种退而求其次的方法并不可行——毕竟软件后面还有很多功能要基于正确的资源尺寸进行进一步的开发。 所以第二次调整改成了更复杂的一个方案:长枪还是32×16的纹理资源,但是每次想进行重新旋转的时候,渲染显示区域都先创建一个32×32的显示区、然后完成旋转之后放在这个32×32的显示区域中。再重新创建一个正确的16×32的显示区,将32×32显示区中的竖直长枪复制出来、写入到16×32的显示区中…… 这虽然是可行的,但是浪费内存、CPU资源不说,更令程序变得十分复杂。要额外的记录很多临时数据、还要计算数值长枪在32×32区域中的正确位置才能准确裁切出来。 以上两个方案都是“自然而然”想到的方案,虽然“思路清晰”、但是“实现复杂”并且总是感觉存在着诸多的隐患。 四、更正确、完美的解决方案 今天我才想明白:其实没有必要让SDL2去进行纹理的旋转,SDL2的SDL_RenderCopyEx()进行纹理旋转有它巨大的优势——对于各种角度的旋转都可以轻松胜任。然而我也用不到这些角度呀,我只需要进行90度、180度、270度的旋转,这几个最基本的旋转就是最基本的矩阵90度旋转。…

控制台中VIM编辑中文时字符鬼影问题的解决

这个问题困扰我好久了,也不记得是什么时候开始的,也许是自从开始使用PowerShell时就出现了这个问题?控制台下使用VIM进行中文编辑,如果当前行有中文,那么这一行的内容编辑、选中,总会在行尾甩出一些鬼影字符。 因为近几年程序写得少、偶尔遇到这个问题就会换用其他编辑器临时救急,所以也就没放在心上。今天实在忍受不了,折腾了一个晚上终于找到了原因并解决了。 在vimrc配置中加入一行 set termguicolors 即可解决: 虽然说加入这行配置之后会导致vim的颜色配置与默认配置有了一些差异,但终归还是“彩色渲染”的代码,修改前后都是“赏心悦目”的状态,因而对于使用也没有什么影响。 这行代码的作用是让vim改用真彩色进行文字渲染,否则vim默认使用的是256色对文本进行渲染。而默认使用256色彩模式时,在显示复杂的 Unicode 字符(如中文字符)时,就有可能导致显示问题——无法正确计算出字符的宽度、从而引起鬼影问题。 上面的解释听起来很牵强,色彩管理和字符宽度能有什么关联呢……但毕竟它真的能解决问题,因而也就不再纠结其中的因果联系了。 奇怪的是用了很多年vim,以前从来没有察觉这个问题,似乎是最近1、2年才出现的问题。毕竟最近很少写代码,偶尔写一写、遇到稀奇古怪的编辑器问题也都是尽量避开,有的时候犯懒甚至就用notepad临时改动几行,所以也没有去深究过。感觉可能是: 1、或者就是自从windows弃用了控制台、启用了powershell之后出现的这个问题; 2、又或者是这个问题一直存在,只不过以前我在windows下一直使用的是neovim所以没有这个问题吧; 不确定,总之,经过上述调整,现在vim又可以正常的对中文内容进行编辑操作了。

使用ε-M极限证明问题一则

为什么? 直观上的感觉是,x趋于无限大,它的导数便趋于0,于是也便趋于0,最终的极限结果就是0了。这似乎很简单。但如果基于数学分析的角度而言,这每一步的理所当然,都需要经过证明,所以对于数学分析而言,这个简单的极限的推导过程,并不轻松,而是要啰里啰唆的说上一大堆,才敢给出结论来的。 它大体上分为两步:1、首先证明;2、然后利用连续函数的复合法则,完成复合函数求极限。以下是每一步的细节: 一、首先使用语言证明: 《普》中并未給出的證明、甚至都沒有提到這一事實,它似乎是默認讀者知道且相信這一極限的結果。這個簡單的極限從直觀上也確實一目瞭然,但是在更嚴謹的數學教材中,其實是會通過語言對它進行證明的。語言和語言相似,都是極限論證語言。使用语言完成证明的过程如下: 1、首先观察原始的题目,并且将原始题目调整一下写法:; 2、这个新的写法不再使用“等号”,从而表达式的意思变成了:当自变量x趋于无限大时,结果(因变量)将趋于0。调整成这样的表达之后,假设这个结论是成立的,那么它将意味着计算的结果与它所趋近于的数值0之间,是存在着一个距离的,而且这个距离可以表达出来,将这个距离表达为:; 3、此时 的含义就是“距离”,并且是 与 之间的距离。我们要证明的是,这个距离 可以任意的小、随意的小,想要多小就多小。换言之,假设 的确可以任意的小能够实现,意味着 与 可以无限接近,便可以将 视为 了; 4、现在直接大胆的提出:当任意指定时,自变量 时,的结果就比还要小。能否找到这个大胆的想法中的M呢?显然是可以的,通过不等式简单的变换,就能确定,也就是说自变量 时的所有x取值,都可以满足 的结果比还要小; 5、至此就可以得出结论:无论怎样小的 指定,都有 M 范围选择点可明确出来。因而可以实现,并且认为。此时的表达式便是:。(这里似乎还缺少对下标 的展开推导)。 二、连续函数的极限连续性:…

再谈昨天的“沃利斯积分“

昨天写的《沃利斯曾经解决过的几道积分问题》还存在一个令我感到“困惑”的问题,今天解决了,很开心。 昨天的文章中提到的积分:,通过换元法可以得到它的三角函数形式、也就是沃利斯积分形式:。而这两个等价的积分式又可以被统称为B积分,进而推导成Gamma计算式,完成求解。 这是昨天的文章中谈到的内容,结论无疑。 但是我的困惑在于:当我对上面诸多计算式,分别通过笔算、SageMath计算、计算器计算对比验证的时候,却发现我使用的计算器无法得到正确的结果,现象如下: 积分 积分式 Gamma结果 SageMath结果 fx-991CN结果 多项式积分 正确 正确 正确 沃利斯积分 正确 正确 1.569…… 问题现象表 可以看出我使用计算器得到的结果并不是正确的,原因在于在这个计算器的设置中有结果表达形式的设置,之前使用的是以数值作为结果表达形式,应该调整成使用弧度作为结果表达形式,通过这个设置的改变,才能得到正确的结果。 之所以要如此繁复的、不厌其烦的做这个验证,主要是心里没底,担心自己理解的不对。现在所有的辅助自动计算结果都与理论吻合、手算结果一致,我就可以放心大胆地说,这个知识点,我算是基本掌握了。 对于计算器,今天在查找它的设置时,额外了解到:计算器在进行积分计算时,使用了很多估算方法,例如辛普森法、梯形法、或者高斯-勒让德积分等。通过这些方法,计算器在给定积分区间内选取若干采样点,以近似计算积分值。 这些数值积分算法对我而言还很陌生、又觉得好奇有趣,只无奈精力实在有限,无法深入学习。