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度旋转。…