This website is under CONSTRUCTION
NPofSi | NaP硅
欢迎来到铃奈庵,这是一个魔法的世界

Recent Articles
对含有 Simscape 的 Simulink 模型中进行程序控制
> 本文基于 Matlab 2023b,Simulink 10.4,Simscape 5.3 编写。 ## 背景 最近学校的一门课程,大作业的任务是通过线路牵引供电模型计算铁路区间上正在行驶的列车所处的位置,其中用到了 Simscape,Matlab 本身对 Simulink 的支持很到位,文档也给的很全,但是对于 Simscape 的部分还是踩了许多坑,这里记录一下,这部分主要集中在文章的后半段;模型本身其实比较简单,所以以下的举例也不是很复杂,主要针对已有模型。 ## 使用 Matlab 读取 Simulink 模型 Simulink 中的功能大多都可以通过 Matlab 程序实现,可以查看 [Simulink - Programmatic Modeling Basics](https://ww2.mathworks.cn/help/simulink/ug/approach-modeling-programmatically.html) ,这里总结一下,对已有模型,在 .m 语言中有以下几个函数可以对模型进行一些文件操作: ```matlab % 打开模型 % 同时会打开 Simulink 的窗口,对比加载模型,同步性会更好 % 也不容易遇到文件锁的问题,这个问题在使用这行代码打开后可以通过关闭 Simulink 窗口解决 open_system('model_name'); % 加载模型 % 只会加载模型,不会打开 Simulink 窗口 % 但如果程序中间异常退出,没有进行到 close_system,会导致模型文件被锁定,无法再次打开,可能需要重启 Matlab load_system('model_name'); % 保存模型 % 保存模型的当前状态,包括参数设置等 % 可以设置 ('OverwriteIfChangedOnDisk',true) 来响应文件系统变更 save_system('model_name'); % 关闭模型 close_system('model_name'); ``` ## 使用 Matlab 编辑 Simulink 模型 首先,在 Simulink 内部,模型的不同组件使用的是像 Linux 文件系统一样的路径来进行标识,例如: ```matlab % 这里的 SubsystemName 和 BlockName 都是模块的名字(如下图模块的名字就是 AT1) block_name = 'model_name/BlockName' block_name1 = 'model_name/SubsystemName/BlockName' block_name2 = 'model_name/SubsystemName/SubsystemName/BlockName' ``` ![一个 Simulink m模块](/images/simulink_block.png) ### 关于 Simulink 模块 可以使用以下几个命令读取或者设置模块的参数: ```matlab % 获取模块参数 get_param(block_name,'parameter_name') % 例如:获取模块句柄 (Handle) % 通过句柄可以对模块进行操作 get_param('model_name/BlockName','Handle') % 设置模块参数 set_param(block_name,'parameter_name','value') % 例如:设置模块位置 set_param('model_name/BlockName','Position',[100,100,200,200]) ``` Matlab 中提供了两个函数可以对模块进行增删: ```matlab % 添加模块 add_block('block_type','model_name/BlockName') % 这个函数也可以用于模块的复制 add_block('model_name/Subsystem1/BlockName','model_name/Subsystem2/BlockName') % 例如:添加一个 Gain 模块 add_block('simulink/Commonly Used Blocks/Gain','model_name/BlockName') % 删除模块 delete_block('model_name/SubsystemName/BlockName') % 例如:删除一个 Gain 模块 delete_block('model_name/BlockName') ``` __Matlab 没有提供模块的移动(剪切)的函数__,这个可以通过以下方法实现(踩的坑之一): ```matlab % 复制模块 add_block('model_name/Subsystem1/BlockName','model_name/Subsystem2/BlockName') % 删除原模块 delete_block('model_name/Subsystem1/BlockName') ``` ### 关于模块的连接 对于 Simscape 模块,建议使用模块的端口句柄来建立连接线,例如: ```matlab % 获取端口句柄(实际上是一个包含端口ID的列表) Block_PortHandle = get_param('model_name/BlockName','PortHandles') ``` __注意__ 大部分 Simscape 电器模块的端口是双向的,所以 __不__ 需要指定端口的方向(即不能使用句柄中的`Inport`和`Outport`,而是使用`LConn`和`RConn`,这两个属性并不区分方向,只是指示端口在模块的左侧还是右侧),这和 Simulink 中的一般模块不同,但 Simscape 模块之间的连接线在程序索引时依然 __区分__ 方向,在这一小节结尾会讨论这件事 模块的连接可以通过以下几个函数实现: ```matlab % 添加连接线 add_line('model_name/Subsystem','BlockName1/Port','BlockName2/Port') % 使用句柄 add_line('model_name/Subsystem',Block1_PortHandle.PortName,Block2_PortHandle.PortName) % 例如:连接两个模块 add_line('model_name/Subsystem','Block1/LConn1','Block2/RConn4') add_line('model_name/Subsystem',Block1_PortHandle.LConn(1),Block2_PortHandle.RConn(4)) % 如果句柄中端口数量一样,可以进行批量连接 add_line('model_name/Subsystem',Block1_PortHandle.LConn,Block2_PortHandle.RConn) % 删除连接线 delete_line('model_name/Subsystem','BlockName1/PortName','BlockName2/PortName') % 例如:删除两个模块的连接 delete_line('model_name/Subsystem','Block1/LConn3','Block/RConn5') % 同理,可以使用句柄 delete_line('model_name/Subsystem',Block1_PortHandle.LConn(3),Block2_PortHandle.RConn(5)) ``` 这里也遇到了两个问题,首先,删除连接线时如果线路不存在,会报错,所以如果可能遇到重复删除的问题,建议套一个 `try catch`,例如: ```matlab try delete_line('model_name/Subsystem',Block1_PortHandle.RConn(4),Block2_PortHandle.LConn(5)) catch end ``` 其次,如果连接线的两端模块不在同一个子系统中,会报错,可能需要移动模块,实现参考上一小节。 最后,关于 Simscape 模块间的连接线,虽然 Simscape 的电气端口不区分方向,但是其连接线仍像 Simulink 中的连接线一样区分方向,所以在进行删除操作时可能会遇到 __明明连接线存在,但是删除时报错的情况__ ,这大概率就是删除时端口的顺序反过来了,所以建议对每个连接线进行两次删除,因为一定会有一次冗余,所以这样操作一定要套上 `try catch` ,例如: ```matlab try delete_line('model_name/Subsystem',Block1_PortHandle.RConn(4),Block2_PortHandle.LConn(5)) catch end try delete_line('model_name/Subsystem',Block2_PortHandle.LConn(5),Block1_PortHandle.RConn(4)) catch end ``` 以上这种情况更有可能发生在已有的手动建立的模型中,手动建立的连接线非常可能起始于不同的模块,但在程序建立连接线时,如果保证在函数中输入的端口顺序都一致的话就不会出现这种问题。 ## 运行 Simulink 仿真 运行模型就比较简单了,只需要调用 `sim` 函数: ```matlab % 直接运行仿真,关于 sim 函数的更多参数请查看文档 % https://ww2.mathworks.cn/help/simulink/slref/sim.html out = sim('model_name'); % 或使用 set_param 方法调用 Simulink 的运行命令 set_param('model_name','SimulationCommand','start'); ``` refs: [Simulink - Programmatic Modeling Basics](https://ww2.mathworks.cn/help/simulink/ug/approach-modeling-programmatically.html)
使用 ssh 访问 Github (从创建密钥到 clone 仓库)
最近在打编译器设计赛,帮队友配置 git 的时候发现配置 ssh 的部分每次要看好几篇文章,现在用这一篇文章就可以在一台新的 Linux 系统上快速使用 ssh 访问 Github 进行开发。 ## 创建 ssh 密钥 ```sh $ ssh-keygen -t ed25519 -C "[email protected]" ``` 如果不支持 ed25519,可以使用: ```sh $ ssh-keygen -t rsa -b 4096 -C "[email protected]" ``` 中间会需要选择密钥的创建位置等: ```text > Enter a file in which to save the key (/c/Users/YOU/.ssh/id_ALGORITHM):[Press enter] ``` 密钥默认生成在 `~/.ssh/id_ALGORITHM`, `ALGORITHM` 即上面创建密钥时使用的算法 `~/.ssh/id_ALGORITHM.pub` 为公钥文件; `~/.ssh/id_ALGORITHM` 为私钥文件; ## 添加 ssh 公钥到 GitHub 执行 ```sh $ cat ~/.ssh/id_ALGORITHM.pub ``` 复制输出的公钥 在 [Github User Setting -> SSH and GPG keys](https://github.com/settings/keys) 页面添加 ssh 公钥。 ## 启用 ssh agent ```sh $ eval "$(ssh-agent -s)" ``` 然后将我们的密钥交给 ssh-agent 管理 ```sh $ ssh-add ~/.ssh/id_ALGORITHM ``` 在 `~/.ssh/` 目录下新建一个 config 文件 ```sh $ touch ~/.ssh/config ``` 编辑加入以下内容: ``` HOST github.com User git ForwardAgent yes ``` ## 测试 现在已经配置好了 ssh 可以用以下命令进行测试: 使用这个命令检查密钥是否被加入 ssh-agent ```sh $ ssh-add -L ``` 使用这个命令检查是否已经可以使用 ssh 访问 Github: ```sh $ ssh -T [email protected] ``` 如果以上命令返回了 success ,那么已经可以使用 ssh 访问 Github了! ## Clone 仓库 接着我们就能用下面的命令 clone 仓库了。 ```sh $ git clone <url> ``` refs: [生成新的 SSH 密钥并将其添加到 ssh-agent](https://docs.github.com/zh/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent) [使用 SSH 代理转发](https://docs.github.com/zh/authentication/connecting-to-github-with-ssh/using-ssh-agent-forwarding)
CMake 学习小结
高考完这段时间点子大概比较多?既然学了两年的信息学奥赛写了两年 C++ ,却根本不会用 C++ 去做开发,这次正好想做的一个东西对性能要求比较高,就考虑用 C++ 来做,又因为原来一直用的 Java 导致我比较关注多平台的兼容,就想趁机学习一下 CMake 这类通用构建工具。这篇文章会列举一下我学到的 CMake 常用的命令,做一个小结。 如果想要学习 CMake ,非常推荐 [CMake 官方提供的教程](https://cmake.org/cmake/help/latest/guide/tutorial/index.html) ,跟下来的话 CMake 基本就可以掌握了。 <!-- more --> ##### 基本信息 这些就是些必填的东西 ```cmake cmake_minimum_required (VERSION 3.8) project (<PROJECT_NAME>) ``` ##### 子目录 用来标记该项目要使用到(存在 CMakeLists.txt)的子目录,<del>致使 CMakeLists.txt 比 源代码 还多的原罪啊</del> ```cmake add_subdirectory(<PATH>) ``` ##### 编译可执行文件 将标记的源代码文件编译成可执行文件 ```cmake add_executable(<EXEC_FILE_NAME> <SOURCE_CODE_FILE。..>) ``` ##### 编译库文件 将标记的源代码文件编译成为链接库文件 ```cmake add_library(<LIB_FILE_NAME> <SOURCE_CODE_FILE。..>) ``` ##### 安装 关于 install 的用法极多,具体还是查阅手册比较好 在我项目里用来引入 Duktape 库 `install(TARGETS duktape DESTINATION lib)` ```cmake install(...) ``` ##### 预编译库的引入 一个 `EXEC_FILE_NAME` 或 `EXEC_FILE_NAME` 可作为 `TARGET_NAME` ,本质上是将该预编译库链接在 obj 文件中,**这也就是说需要先存在一个 `EXEC_FILE_NAME` 或 `EXEC_FILE_NAME`,请保证调用此命令前调用过相应的 `add_executable` 或 `add_library`,否则会报 ` Cannot specify include directories when use target target_include_directories` 错误** ```cmake target_link_libraries(<TARGET_NAME> <PUBLIC|SHARED|PRIVATE> <LIB_NAME>) ``` ##### 头文件目录的引入 经常要用的,我的项目中是用了很多 stb 项目的头文件 `target_include_directories(${PROJECT_NAME} PUBLIC stb)`,**`TARGET_NAME` 的要求同上** ```cmake target_include_directories(<TARGET_NAME> <PUBLIC|SHARED|PRIVATE> <DIR_NAME>) ``` ##### 获取文件夹下所有源文件 将根目录下所有文件文件名转储到一个变量里,在添加可执行文件时的例子: `add_executable (${PROJECT_NAME} ${<VARIABLE_NAME>})"` ```cmake aux_source_directory(. <VARIABLE_NAME>) ``` ##### 生成 Config 文件 在编译时生成 config 文件以便在不同要求,不同操作系统下编译或输出文件 ```cmake configure_file(Config.h.in Config.h) ```
Android ContentProvider 使用小计
Android 应用在选取文件时推荐使用 `Intent.ACTION_GET_CONTENT` 意图,而不是使用应用自己创建的文件选择器并获取读取文件权限。 因为从 Android Marshmallow 开始 Android 权限管理开始变得更加整顿严谨,几个版本以来 Android 解决了权限管理松散的问题。 其实 `ContentProvider` 并不是什么新事物,在 Android 中一直存在着,但随着权限的明确划分,权限的索取要更加的明确,否则会极大的影响用户体验,这时使用系统给予的机制会更加符合操作逻辑,也可以减少工作量。 <!-- more --> 下面是一个选取文件的简单使用场景 ```java //需要指定一个 int 来定位返回值 final int REQUEST_GET_CONTENT=1 //调用系统文件选择器 public void getContent() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(intent, REQUEST_GET_CONTENT); } ``` ```java //接受 URI @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { switch (requestCode) { case REQUEST_GET_CONTENT: Uri uri=data.getData(); break; } } } ``` 之后要读取这个文件的内容只需要调用 `getContentResolver().openInputStream(uri)` 就可以了 但是! 这里有个问题 这样得不到文件的名字 直到我在 Android 文档里翻了又翻翻了又翻翻了又翻————官方还是很良心的,只是忘了用一下搜索功能。。。。 ```java public String getContentName(Uri uri) { String result = null; if (uri.getScheme().equals("content")) { Cursor cursor = this.getContentResolver().query(uri, null, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); } } finally { cursor.close(); } } if (result == null) { result = uri.getPath(); int cut = result.lastIndexOf('/'); if (cut != -1) { result = result.substring(cut + 1); } } return result; } ``` 文档里直接把这个函数给出来了,就直接拿来放心用就好了 后来搜索相关的东西的时候在 stackoverflow 也发现了这个话题,回答是一样的。 这些基本就是选取文件完全方法了,虽然很水,不过写出来就不容易忘了吧 以上
距高考那 一百天
距离高考还有一百天了,有点不舍,两年半来的生活好像箭一般的飞过,只不过是三个月,就算对不起什么,也不能让高中的时光过得没有任何意义。~~一个月前还在推 gal 的人不要说话~~ <!-- more --> 不论是为了百日之后,还是为了四年之后,我想应该去尝试一下了。也曾有的时候会迷茫,失去目标,但当我每每落下笔,每每拿起一本书,我就能想起,不论是自由,还是责任,只要至少其一存在,就应该去努力。 > 正是因为认真,所以才无法隐藏或逃避内心的悔恨和惋惜。只有放手去做,才能抹平心中的悔恨和惋惜,就算再失败,再失败,也只能去做。 也得克服一下自己的**拖延症**了,确实是经常把小事安排在前面导致后面工作量大的事一拖再拖。据说先做工作量大的工作就不容易拖延,~~那怕不是连小事也做不完了(误)~~,希望这能起作用。 顺便这里也列一下高考完想做的事吧 - 去哪里游览一次,非常想去荷兰看看,那里也有我想考研去的学校w - 学习一下 Verilog 等硬件描述语言 - 把几个开了的 Android 应用完结掉,试试开发 PC 端的软件 - 本来兴趣就在物理的,毕业以后要把《力学概论》读完,顺便看一下电气相关的书 - 2020 年有好多的番,电影,音乐 ,想买鹿乃的专辑 这些天里要下下辛苦呢,**否则时间就这样过去也是一种浪费啊(๑•﹏•)。**
NOIp 2018 复赛总结
在这个星期日,经历了长达两天的 NOIp 复试,是时候做个总结了( ;∀;) ( tcl )<del>( 好像 www.noi.cn 出了点问题)</del> <!-- more --> 只有第一天前两道题能确切的拿到分啊 / 第二天的题完全没有希望::= 而且考完 NOIp 就期中考试真的没关系? 总感觉自己好弱,( dalao 们求罩了) 现在还是正经点说一下我做出来的部分qwq ##### DAY1 T1 修路 我的思路和 luogu 的题解有些不同。 - 我定义了区间数组 ` int arr[100002];` - 然后用这个数组储存了整条路(区间)。 - 写一个函数 `check()` 判断 `arr[1]~arr[n]` 是否全为零(已填平)。 - 写一个函数 `fill()` 从 `arr[1]` 开始向后寻找连续的不为零的区段,并找出最浅的地方,并把每个地方的深度修改为 `当前深度 - 最浅深度` (把该地向上填) 并使答案增加最浅的深度(一天填一层所以 “天数 = 深度” ) - 因为保证初始区间全不为零,所以使用 do-while : ``` do{ fill(); }while(check()); ``` 并没有彻底模拟每一天填的状况。 > 这个算法的算法复杂度在 O(n^2) 到 O(n). > 具体取决于输入数据,如果是金字塔形的就会被逼到 O(n^2) 如果是崎岖不平的而差别不太大的数据 复杂度就接近 O(n) ##### DAY1 T2 [货币系统](https://www.luogu.com.cn/problem/P5020) 一开始自己感觉没有思路,不过回想了一下学过的知识发现这道题就是一个很常规的筛法,如果用 dfs 的话第二个样例就吃不消了, ~~尝试写了一下 dp 还是太菜就放弃了~~ ,最后用 dfs 只拿了60分。 ##### DAY1 T3 [赛道修建](https://www.luogu.org/problem/P5021) 这道题有点思路,想到了二分,但是不会写啊,时间也很紧,然后就丢了(哭)。 ##### DAY2 T1 旅行 这道题看了一会儿就知道要干什么了,匆匆忙忙开始写。结果写完一后只过了一个样例,另一个样例完全不符,十分头疼。半个小时也没想出来是为什么。 考完后来去看别人题解,原来是要枚举边,而我枚举的是点。 估计是出题人好心,测试数据中有两个是枚举点也能过得,最后拿了 20 分。 ##### DAY2 T2 填数游戏 这道题一开始拿来还是很自信的,毕竟是规律题,做不出来还可以猜,结果我琢磨了半个小时也没琢磨明白,去猜的过程中手算也算不了几个数据,只能发现两条边上的结果是 2<sup>n</sup> ,就只写了这么多,最后拿了 15 分虽然有些不甘心,但是总比扔掉要强。 ##### DAY2 T3 保卫王国 这道题看完题我觉得是要用 dp 就写,接着发现是动态 dp 就瞬间放弃,我 dp 本来就不熟练,加点东西就更废了,时间也不够了,这道题也只好扔掉了。