仿真配置文件解析器
仿真软件图像
一个典型仿真软件的前后端是分离的,通过一个配置文件实现前后端的信息传递:
前端界面 :
用户通过图形用户界面(GUI)或命令行界面(CLI)进行交互。
用户选择或设置仿真的各项参数、模型、边界条件等。
前端界面将这些选择和设置转换为一个配置文件(JSON、XML、YAML等格式),并保存或直接传递给后端求解器。
配置文件 :
配置文件包含仿真所需的所有信息,包含几何结构、材料属性、网格划分、边界条件、求解算法和输出选项等。
配置文件是独立于主程序的,可以编辑或重用它来进行不同的仿真任务。
后端求解器 :
后端求解器读取并解析配置文件,提取其中的参数和设置。
基于配置文件中的信息,实例化相应的对象(如几何体、材料、网格、边界条件等)。
求解器按照仿真流程进行计算,包括具体的数值求解、迭代、结果存储等操作。
仿真完成后,求解器生成输出文件或数据,包括结果文件、日志文件、图形输出等。
结果输出与可视化 :
仿真结果可以保存为文件,也可以通过前端界面直接展示。
前端界面可以解析并可视化这些结果,提供直观的分析工具。
在这种结构下,前端和后端通过配置文件解耦,前端界面可以是任意的(GUI、CLI),而后端求解器只关心解析和执行配置文件中的任务。用户可以通过配置文件自定义仿真任务,不需要源代码重新编译,这也是用编译型语言实现仿真任意任务的方式。对于解释型语言,配置文件其实也是可以不需要的,用户直接写一个.py
脚本用解释器动态执行就可以了。在商业软件中,前后端都是要有的。对于Lammps这种开源科学计算程序,一般没有前端界面。用户编辑一个配置文件来描述自己的仿真体系信息,求解器读取解析后直接完成对应的仿真,用户自行进行后处理。
配置文件和工厂模式
假设我们正在使用JSON格式的配置文件,想要编写一个解析器根据配置文件生成相应的对象。比如如下所示用户定义了两个几何对象,一个长方体一个圆柱体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 data = { "geometry" : [ { "no" : 1 , "type" : "cuboid" , "corner" : { "x" : 0.0 , "y" : 0.0 , "z" : 0.0 } , "length" : { "width" : 1.0 , "height" : 2.0 , "depth" : 1.0 } } , { "no" : 2 , "type" : "cylinder" , "radius" : 2 , "height" : 3 , "center" : { "x" : 1 , "y" : 0 , "z" : 0 } , "normal" : { "x" : 0 , "y" : 1 , "z" : 0 } } ] }
几何类满足如下的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from abc import ABC, abstractmethodclass Geometry (ABC ): @abstractmethod def some_method (self ): pass class Cuboid (Geometry ): def __init__ (self, corner, length ): self.corner = corner self.length = length def __repr__ (self ): return "I'm a cuboid with length {}" .format (self.corner, self.length) def some_method (self ): pass class Cylinder (Geometry ): def __init__ (self, radius, height, center, normal ): self.radius = radius self.height = height self.center = center self.normal = normal def __repr__ (self ): return "I'm a cylinder with radius {} and height {}" .format (self.radius, self.height) def some_method (self ): pass
此时就可以用工厂模式来实现这个解析器的设计。首先为所有的几何解析器创建一个接口,让parse
方法创建一个具体的几何对象,具体的几何解析器实现各自的接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class GeometryParser (ABC ): @abstractmethod def parse (self, data ): pass class CuboidParser (GeometryParser ): def parse (self, data ): corner = data['corner' ] length = data['length' ] return Cuboid(corner, length) class CylinderParser (GeometryParser ): def parse (self, data ): radius = data['radius' ] height = data['height' ] center = data['center' ] normal = data['normal' ] return Cylinder(radius, height, center, normal)
进一步创建一个工厂选择器,根据几何类型返回相应的解析器实例对象。
1 2 3 4 5 6 7 8 9 10 11 12 class GeometryParserSelector : parsers = { "cuboid" : CuboidParser(), "cylinder" : CylinderParser() } @staticmethod def get_parser (geometry_type ) -> GeometryParser: parser = GeometryParserSelector.parsers.get(geometry_type) if not parser: raise ValueError(f"Unknown geometry type: {geometry_type} " ) return parser
执行可以获得生成的几何对象:
1 2 3 4 generated_geos = [ GeometryParserSelector.get_parser(geo_info['type' ]).parse(geo_info) for geo_info in geo_data['geometry' ] ]
1 2 [I'm a cuboid with length {'x': 0.0, 'y': 0.0, 'z': 0.0}, I'm a cylinder with radius 2 and height 3]